mirror of
git://xwords.git.sourceforge.net/gitroot/xwords/xwords
synced 2025-01-16 15:41:16 +01:00
Merge remote branch 'origin/android_localize' into android_localize
This commit is contained in:
commit
b4d5130588
32 changed files with 1532 additions and 1031 deletions
|
@ -97,7 +97,7 @@
|
|||
<activity android:name="SMSInviteActivity"
|
||||
android:theme="@android:style/Theme.Dialog"
|
||||
android:configChanges="keyboardHidden|orientation|screenSize"
|
||||
android:screenOrientation="portrait"
|
||||
android:screenOrientation="sensor"
|
||||
/>
|
||||
|
||||
<activity android:name="GameConfigActivity"
|
||||
|
@ -208,6 +208,14 @@
|
|||
<intent-filter android:priority="999" >
|
||||
<action android:name="android.provider.Telephony.SMS_RECEIVED" />
|
||||
</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>
|
||||
|
||||
<service android:name="SMSService"/>
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -9,6 +9,7 @@
|
|||
|
||||
<h3>New with this release</h3>
|
||||
<ul>
|
||||
<li>Display board in upside-down portrait orientation too (where OS version allows)</li>
|
||||
<li>Bug fix (obscure): don't mangle unicode model names</li>
|
||||
</ul>
|
||||
|
||||
|
|
|
@ -41,6 +41,7 @@
|
|||
<string name="key_dict_host">key_dict_host3</string>
|
||||
<string name="key_logging_on">key_logging_on</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_nethintsallowed">key_init_nethintsallowed</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_summary">Menuitems etc. (release builds
|
||||
only)</string>
|
||||
<string name="title_send_data_sms">Send SMS as data</string>
|
||||
<string name="summary_send_data_sms">(GSM phones only)</string>
|
||||
<string name="board_menu_game_netstats">Network stats</string>
|
||||
<string name="netstats_title">Game network stats</string>
|
||||
|
||||
|
|
|
@ -886,7 +886,7 @@
|
|||
<string name="summary_field">Include in game listing</string>
|
||||
|
||||
<!-- Put nothing in the summary space, so it just reads "Game 2" -->
|
||||
<string name="game_summary_field_empty">\u003cNothing\u003E</string>
|
||||
<string name="game_summary_field_empty">\u003CNothing\u003E</string>
|
||||
<!-- Put the language there, so it reads "Game 2 (English)" -->
|
||||
<string name="game_summary_field_language">Game language#1</string>
|
||||
<!-- List names of opponents (summarized), e.g. "Game 2 (vs Kati)" -->
|
||||
|
@ -1200,13 +1200,13 @@
|
|||
substituted for "%1$s". (The funky \u003c and friends are
|
||||
encodings for the greater-than and less-than symbols which
|
||||
are not legal in xml strings.)-->
|
||||
<string name="invite_htm_fmt">\u003ca href=\"%1$s\"\u003ETap
|
||||
here\u003c/a\u003E (or tap the full link below, or, if you already
|
||||
<string name="invite_htm_fmt">\u003Ca href=\"%1$s\"\u003ETap
|
||||
here\u003C/a\u003E (or tap the full link below, or, if you already
|
||||
have Crosswords installed, open the attachment) to accept my
|
||||
invitation and join this game.
|
||||
|
||||
\u003cbr \\\u003E
|
||||
\u003cbr \\\u003E
|
||||
\u003Cbr \\\u003E
|
||||
\u003Cbr \\\u003E
|
||||
(full link: %1$s )
|
||||
</string>
|
||||
|
||||
|
@ -2235,5 +2235,7 @@
|
|||
|
||||
<string name="xlations_enabled_title">Enable local translating</string>
|
||||
<string name="xlations_enabled_summary">Add option to every screen menu</string>
|
||||
|
||||
<string name="data_gsm_only">SMS Data is only available on GSM phones.</string>
|
||||
|
||||
</resources>
|
||||
|
|
|
@ -344,6 +344,11 @@
|
|||
android:title="Show SMS sends, receives"
|
||||
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="false"
|
||||
/>
|
||||
<org.eehouse.android.xw4.XWEditTextPreference
|
||||
android:key="@string/key_relay_host"
|
||||
android:title="@string/relay_host"
|
||||
|
|
|
@ -21,68 +21,17 @@
|
|||
package org.eehouse.android.xw4;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Dialog;
|
||||
import android.app.ProgressDialog;
|
||||
|
||||
import android.content.DialogInterface.OnDismissListener;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.ActivityInfo;
|
||||
import android.content.res.Configuration;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Rect;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Message;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import android.view.KeyEvent;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.Window;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.HashSet;
|
||||
import java.util.concurrent.Semaphore;
|
||||
import junit.framework.Assert;
|
||||
|
||||
import org.eehouse.android.xw4.DlgDelegate.Action;
|
||||
import org.eehouse.android.xw4.jni.*;
|
||||
import org.eehouse.android.xw4.jni.CommsAddrRec.CommsConnType;
|
||||
import org.eehouse.android.xw4.jni.CurGameInfo.DeviceRole;
|
||||
import org.eehouse.android.xw4.jni.JNIThread.*;
|
||||
|
||||
|
||||
public class BoardActivity extends Activity {
|
||||
|
||||
public static final String INTENT_KEY_CHAT = "chat";
|
||||
|
||||
private static final int CHAT_REQUEST = 1;
|
||||
private static final int BT_INVITE_RESULT = 2;
|
||||
private static final int SMS_INVITE_RESULT = 3;
|
||||
|
||||
private static final int SCREEN_ON_TIME = 10 * 60 * 1000; // 10 mins
|
||||
|
||||
private static final String DLG_TITLE = "DLG_TITLE";
|
||||
private static final String DLG_TITLESTR = "DLG_TITLESTR";
|
||||
private static final String DLG_BYTES = "DLG_BYTES";
|
||||
private static final String ROOM = "ROOM";
|
||||
private static final String PWDNAME = "PWDNAME";
|
||||
private static final String TOASTSTR = "TOASTSTR";
|
||||
private static final String WORDS = "WORDS";
|
||||
private static final String GETDICT = "GETDICT";
|
||||
|
||||
private BoardDelegate m_dlgt;
|
||||
|
||||
@Override
|
||||
|
@ -90,7 +39,7 @@ public class BoardActivity extends Activity {
|
|||
{
|
||||
Dialog dialog = super.onCreateDialog( id );
|
||||
if ( null == dialog ) {
|
||||
dialog = m_dlgt.createDialog( id );
|
||||
dialog = m_dlgt.onCreateDialog( id );
|
||||
}
|
||||
return dialog;
|
||||
} // onCreateDialog
|
||||
|
@ -106,6 +55,12 @@ public class BoardActivity extends Activity {
|
|||
protected void onCreate( Bundle savedInstanceState )
|
||||
{
|
||||
super.onCreate( savedInstanceState );
|
||||
|
||||
if ( 9 <= Integer.valueOf( android.os.Build.VERSION.SDK ) ) {
|
||||
setRequestedOrientation( ActivityInfo.
|
||||
SCREEN_ORIENTATION_SENSOR_PORTRAIT );
|
||||
}
|
||||
|
||||
m_dlgt = new BoardDelegate( this, savedInstanceState );
|
||||
m_dlgt.init( savedInstanceState );
|
||||
} // onCreate
|
||||
|
|
|
@ -236,9 +236,9 @@ public class BoardDelegate extends DelegateBase
|
|||
}
|
||||
}
|
||||
|
||||
protected Dialog createDialog( int id )
|
||||
protected Dialog onCreateDialog( int id )
|
||||
{
|
||||
Dialog dialog = super.createDialog( id );
|
||||
Dialog dialog = super.onCreateDialog( id );
|
||||
if ( null == dialog ) {
|
||||
DialogInterface.OnClickListener lstnr;
|
||||
AlertDialog.Builder ab = makeAlertBuilder();
|
||||
|
|
|
@ -101,7 +101,7 @@ public class ChatDelegate extends DelegateBase
|
|||
DBUtils.appendChatHistory( m_activity, m_rowid, text, true );
|
||||
|
||||
Intent result = new Intent();
|
||||
result.putExtra( BoardActivity.INTENT_KEY_CHAT, text );
|
||||
result.putExtra( BoardDelegate.INTENT_KEY_CHAT, text );
|
||||
m_activity.setResult( Activity.RESULT_OK, result );
|
||||
}
|
||||
finish();
|
||||
|
|
|
@ -122,7 +122,7 @@ public class DelegateBase implements DlgDelegate.DlgClickNotify,
|
|||
m_delegate.showDialog( dlgID );
|
||||
}
|
||||
|
||||
protected Dialog createDialog( int id )
|
||||
protected Dialog onCreateDialog( int id )
|
||||
{
|
||||
return m_delegate.createDialog( id );
|
||||
}
|
||||
|
|
|
@ -87,7 +87,7 @@ public class DictsActivity extends ExpandableListActivity {
|
|||
{
|
||||
Dialog dialog = super.onCreateDialog( id );
|
||||
if ( null == dialog ) {
|
||||
dialog = m_dlgt.createDialog( id );
|
||||
dialog = m_dlgt.onCreateDialog( id );
|
||||
}
|
||||
return dialog;
|
||||
} // onCreateDialog
|
||||
|
|
|
@ -234,7 +234,7 @@ public class DictsDelegate extends DelegateBase
|
|||
m_activity = activity;
|
||||
}
|
||||
|
||||
protected Dialog createDialog( int id )
|
||||
protected Dialog onCreateDialog( int id )
|
||||
{
|
||||
OnClickListener lstnr, lstnr2;
|
||||
Dialog dialog;
|
||||
|
@ -341,7 +341,7 @@ public class DictsDelegate extends DelegateBase
|
|||
break;
|
||||
|
||||
default:
|
||||
dialog = super.createDialog( id );
|
||||
dialog = super.onCreateDialog( id );
|
||||
doRemove = false;
|
||||
break;
|
||||
}
|
||||
|
@ -351,7 +351,7 @@ public class DictsDelegate extends DelegateBase
|
|||
}
|
||||
|
||||
return dialog;
|
||||
} // createDialog
|
||||
} // onCreateDialog
|
||||
|
||||
protected void prepareDialog( int id, Dialog dialog )
|
||||
{
|
||||
|
|
|
@ -48,7 +48,7 @@ public class GamesListActivity extends ListActivity {
|
|||
{
|
||||
Dialog dialog = super.onCreateDialog( id );
|
||||
if ( null == dialog && null != m_dlgt ) {
|
||||
dialog = m_dlgt.createDialog( id );
|
||||
dialog = m_dlgt.onCreateDialog( id );
|
||||
}
|
||||
return dialog;
|
||||
} // onCreateDialog
|
||||
|
|
|
@ -127,7 +127,7 @@ public class GamesListDelegate extends DelegateBase
|
|||
m_activity = activity;
|
||||
}
|
||||
|
||||
protected Dialog createDialog( int id )
|
||||
protected Dialog onCreateDialog( int id )
|
||||
{
|
||||
Dialog dialog = null;
|
||||
DialogInterface.OnClickListener lstnr;
|
||||
|
@ -331,7 +331,7 @@ public class GamesListDelegate extends DelegateBase
|
|||
break;
|
||||
|
||||
default:
|
||||
dialog = super.createDialog( id );
|
||||
dialog = super.onCreateDialog( id );
|
||||
break;
|
||||
}
|
||||
return dialog;
|
||||
|
|
|
@ -25,6 +25,8 @@ import android.content.SharedPreferences;
|
|||
import android.os.Bundle;
|
||||
import android.preference.PreferenceActivity;
|
||||
|
||||
import org.eehouse.android.xw4.loc.LocUtils;
|
||||
|
||||
public class PrefsActivity extends PreferenceActivity {
|
||||
|
||||
private PrefsDelegate m_dlgt;
|
||||
|
@ -38,12 +40,18 @@ public class PrefsActivity extends PreferenceActivity {
|
|||
@Override
|
||||
protected void onCreate( Bundle savedInstanceState )
|
||||
{
|
||||
super.onCreate( savedInstanceState );
|
||||
|
||||
m_dlgt = new PrefsDelegate( this, savedInstanceState );
|
||||
super.onCreate( savedInstanceState );
|
||||
m_dlgt.init( savedInstanceState );
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStart()
|
||||
{
|
||||
LocUtils.xlatePreferences( this );
|
||||
super.onStart();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume()
|
||||
{
|
||||
|
|
|
@ -42,6 +42,7 @@ public class PrefsDelegate extends DelegateBase
|
|||
private String m_keyLogging;
|
||||
private String m_smsToasting;
|
||||
private String m_smsEnable;
|
||||
private String m_smsData;
|
||||
private String m_downloadPath;
|
||||
private String m_thumbSize;
|
||||
private String m_hideTitle;
|
||||
|
@ -55,71 +56,73 @@ public class PrefsDelegate extends DelegateBase
|
|||
|
||||
protected Dialog onCreateDialog( int id )
|
||||
{
|
||||
Dialog dialog = null;
|
||||
DialogInterface.OnClickListener lstnr = null;
|
||||
int confirmID = 0;
|
||||
Dialog dialog = super.onCreateDialog( id );
|
||||
if ( null == dialog ) {
|
||||
DialogInterface.OnClickListener lstnr = null;
|
||||
int confirmID = 0;
|
||||
|
||||
switch( DlgID.values()[id] ) {
|
||||
case REVERT_COLORS:
|
||||
confirmID = R.string.confirm_revert_colors;
|
||||
lstnr = new DialogInterface.OnClickListener() {
|
||||
public void onClick( DialogInterface dlg, int item ) {
|
||||
SharedPreferences sp = getSharedPreferences();
|
||||
SharedPreferences.Editor editor = sp.edit();
|
||||
int[] colorKeys = {
|
||||
R.string.key_player0,
|
||||
R.string.key_player1,
|
||||
R.string.key_player2,
|
||||
R.string.key_player3,
|
||||
R.string.key_bonus_l2x,
|
||||
R.string.key_bonus_l3x,
|
||||
R.string.key_bonus_w2x,
|
||||
R.string.key_bonus_w3x,
|
||||
R.string.key_tile_back,
|
||||
R.string.key_clr_crosshairs,
|
||||
R.string.key_empty,
|
||||
R.string.key_background,
|
||||
R.string.key_clr_bonushint,
|
||||
};
|
||||
for ( int colorKey : colorKeys ) {
|
||||
editor.remove( getString(colorKey) );
|
||||
switch( DlgID.values()[id] ) {
|
||||
case REVERT_COLORS:
|
||||
confirmID = R.string.confirm_revert_colors;
|
||||
lstnr = new DialogInterface.OnClickListener() {
|
||||
public void onClick( DialogInterface dlg, int item ) {
|
||||
SharedPreferences sp = getSharedPreferences();
|
||||
SharedPreferences.Editor editor = sp.edit();
|
||||
int[] colorKeys = {
|
||||
R.string.key_player0,
|
||||
R.string.key_player1,
|
||||
R.string.key_player2,
|
||||
R.string.key_player3,
|
||||
R.string.key_bonus_l2x,
|
||||
R.string.key_bonus_l3x,
|
||||
R.string.key_bonus_w2x,
|
||||
R.string.key_bonus_w3x,
|
||||
R.string.key_tile_back,
|
||||
R.string.key_clr_crosshairs,
|
||||
R.string.key_empty,
|
||||
R.string.key_background,
|
||||
R.string.key_clr_bonushint,
|
||||
};
|
||||
for ( int colorKey : colorKeys ) {
|
||||
editor.remove( getString(colorKey) );
|
||||
}
|
||||
editor.commit();
|
||||
relaunch();
|
||||
}
|
||||
editor.commit();
|
||||
relaunch();
|
||||
}
|
||||
};
|
||||
break;
|
||||
case REVERT_ALL:
|
||||
confirmID = R.string.confirm_revert_all;
|
||||
lstnr = new DialogInterface.OnClickListener() {
|
||||
public void onClick( DialogInterface dlg, int item ) {
|
||||
SharedPreferences sp = getSharedPreferences();
|
||||
SharedPreferences.Editor editor = sp.edit();
|
||||
editor.clear();
|
||||
editor.commit();
|
||||
relaunch();
|
||||
}
|
||||
};
|
||||
break;
|
||||
case CONFIRM_SMS:
|
||||
dialog = SMSCheckBoxPreference.onCreateDialog( m_activity, id );
|
||||
break;
|
||||
case EXPLAIN_TITLE:
|
||||
dialog = LocUtils.makeAlertBuilder( m_activity )
|
||||
.setMessage( R.string.no_hide_titlebar )
|
||||
.setTitle( R.string.info_title )
|
||||
.setPositiveButton( R.string.button_ok, null )
|
||||
.create();
|
||||
break;
|
||||
}
|
||||
};
|
||||
break;
|
||||
case REVERT_ALL:
|
||||
confirmID = R.string.confirm_revert_all;
|
||||
lstnr = new DialogInterface.OnClickListener() {
|
||||
public void onClick( DialogInterface dlg, int item ) {
|
||||
SharedPreferences sp = getSharedPreferences();
|
||||
SharedPreferences.Editor editor = sp.edit();
|
||||
editor.clear();
|
||||
editor.commit();
|
||||
relaunch();
|
||||
}
|
||||
};
|
||||
break;
|
||||
case CONFIRM_SMS:
|
||||
dialog = SMSCheckBoxPreference.onCreateDialog( m_activity, id );
|
||||
break;
|
||||
case EXPLAIN_TITLE:
|
||||
dialog = LocUtils.makeAlertBuilder( m_activity )
|
||||
.setMessage( R.string.no_hide_titlebar )
|
||||
.setTitle( R.string.info_title )
|
||||
.setPositiveButton( R.string.button_ok, null )
|
||||
.create();
|
||||
break;
|
||||
}
|
||||
|
||||
if ( null == dialog && null != lstnr ) {
|
||||
dialog = LocUtils.makeAlertBuilder( m_activity )
|
||||
.setTitle( R.string.query_title )
|
||||
.setMessage( confirmID )
|
||||
.setPositiveButton( R.string.button_ok, lstnr )
|
||||
.setNegativeButton( R.string.button_cancel, null )
|
||||
.create();
|
||||
if ( null == dialog && null != lstnr ) {
|
||||
dialog = LocUtils.makeAlertBuilder( m_activity )
|
||||
.setTitle( R.string.query_title )
|
||||
.setMessage( confirmID )
|
||||
.setPositiveButton( R.string.button_ok, lstnr )
|
||||
.setNegativeButton( R.string.button_cancel, null )
|
||||
.create();
|
||||
}
|
||||
}
|
||||
return dialog;
|
||||
}
|
||||
|
@ -133,6 +136,7 @@ public class PrefsDelegate extends DelegateBase
|
|||
m_keyLogging = getString( R.string.key_logging_on );
|
||||
m_smsToasting = getString( R.string.key_show_sms );
|
||||
m_smsEnable = getString( R.string.key_enable_sms );
|
||||
m_smsData = getString( R.string.key_send_data_sms );
|
||||
m_downloadPath = getString( R.string.key_download_path );
|
||||
m_thumbSize = getString( R.string.key_thumbsize );
|
||||
m_hideTitle = getString( R.string.key_hide_title );
|
||||
|
@ -165,6 +169,7 @@ public class PrefsDelegate extends DelegateBase
|
|||
@Override
|
||||
public void onSharedPreferenceChanged( SharedPreferences sp, String key )
|
||||
{
|
||||
// DbgUtils.logf( "onSharedPreferenceChanged(key=%s)", key );
|
||||
if ( key.equals( m_keyLogging ) ) {
|
||||
DbgUtils.logEnable( sp.getBoolean( key, false ) );
|
||||
} else if ( key.equals( m_smsToasting ) ) {
|
||||
|
@ -176,6 +181,13 @@ public class PrefsDelegate extends DelegateBase
|
|||
SMSService.stopService( m_activity );
|
||||
XWPrefs.setHaveCheckedSMS( m_activity, false );
|
||||
}
|
||||
} else if ( key.equals( m_smsData ) ) {
|
||||
boolean turningOn = sp.getBoolean( key, true );
|
||||
if ( turningOn && !Utils.isGSMPhone( m_activity ) ) {
|
||||
showOKOnlyDialog( R.string.data_gsm_only );
|
||||
((CheckBoxPreference)(m_activity.findPreference( key )))
|
||||
.setChecked( false );
|
||||
}
|
||||
} else if ( key.equals( m_downloadPath ) ) {
|
||||
String value = sp.getString( key, null );
|
||||
if ( null != value ) {
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,6 +29,7 @@ import android.content.Context;
|
|||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
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 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;
|
||||
|
@ -70,6 +72,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";
|
||||
|
@ -77,13 +80,15 @@ public class SMSService extends XWService {
|
|||
private static final String PHONE = "PHONE";
|
||||
|
||||
private static Boolean s_showToasts = null;
|
||||
|
||||
private static Boolean s_asData = null;
|
||||
|
||||
// All messages are base64-encoded byte arrays. The first byte is
|
||||
// always one of these. What follows depends.
|
||||
private enum SMS_CMD { NONE, INVITE, DATA, DEATH, ACK, };
|
||||
|
||||
private BroadcastReceiver m_sentReceiver;
|
||||
private BroadcastReceiver m_receiveReceiver;
|
||||
private OnSharedPreferenceChangeListener m_prefsListener;
|
||||
|
||||
private int m_nReceived = 0;
|
||||
private static int s_nSent = 0;
|
||||
|
@ -119,6 +124,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,
|
||||
|
@ -201,10 +215,8 @@ public class SMSService extends XWService {
|
|||
private static Intent getIntentTo( Context context, int cmd )
|
||||
{
|
||||
if ( null == s_showToasts ) {
|
||||
SharedPreferences sp
|
||||
= PreferenceManager.getDefaultSharedPreferences( context );
|
||||
String key = LocUtils.getString( context, R.string.key_show_sms );
|
||||
s_showToasts = sp.getBoolean( key, false );
|
||||
s_showToasts =
|
||||
XWPrefs.getPrefsBoolean( context, R.string.key_show_sms, false );
|
||||
}
|
||||
|
||||
Intent intent = new Intent( context, SMSService.class );
|
||||
|
@ -233,6 +245,13 @@ public class SMSService extends XWService {
|
|||
unregisterReceiver( m_receiveReceiver );
|
||||
m_receiveReceiver = null;
|
||||
}
|
||||
if ( null != m_prefsListener ) {
|
||||
SharedPreferences sp
|
||||
= PreferenceManager.getDefaultSharedPreferences( this );
|
||||
sp.unregisterOnSharedPreferenceChangeListener( m_prefsListener );
|
||||
m_prefsListener = null;
|
||||
}
|
||||
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
|
@ -257,6 +276,7 @@ public class SMSService extends XWService {
|
|||
}
|
||||
break;
|
||||
case HANDLE:
|
||||
case HANDLEDATA:
|
||||
++m_nReceived;
|
||||
ConnStatusHandler.
|
||||
updateStatusIn( this, null,
|
||||
|
@ -264,9 +284,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:
|
||||
|
@ -388,13 +413,24 @@ 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 ( 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 )
|
||||
throws java.io.IOException
|
||||
private String[] breakAndEncode( String msg ) throws java.io.IOException
|
||||
{
|
||||
// TODO: as optimization, truncate header when only one packet
|
||||
// required
|
||||
|
@ -418,6 +454,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 =
|
||||
|
@ -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 )
|
||||
{
|
||||
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,
|
||||
int count, String msg )
|
||||
{
|
||||
|
@ -504,18 +608,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 );
|
||||
|
@ -593,6 +720,39 @@ public class SMSService extends XWService {
|
|||
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,
|
||||
int gameID, String gameName,
|
||||
int lang, String dict,
|
||||
|
@ -691,14 +851,39 @@ public class SMSService extends XWService {
|
|||
@Override
|
||||
public void onReceive(Context arg0, Intent arg1)
|
||||
{
|
||||
if ( Activity.RESULT_OK == getResultCode() ) {
|
||||
DbgUtils.logf( "SUCCESS!!!" );
|
||||
} else {
|
||||
DbgUtils.logf( "FAILURE!!!" );
|
||||
}
|
||||
DbgUtils.logf( "SMS delivery result: %s",
|
||||
Activity.RESULT_OK == getResultCode()
|
||||
? "SUCCESS" : "FAILURE" );
|
||||
}
|
||||
};
|
||||
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 {
|
||||
|
@ -729,41 +914,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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -75,7 +75,7 @@ public class StudyListActivity extends ListActivity {
|
|||
@Override
|
||||
protected Dialog onCreateDialog( int id )
|
||||
{
|
||||
Dialog dialog = m_dlgt.createDialog( id );
|
||||
Dialog dialog = m_dlgt.onCreateDialog( id );
|
||||
if ( null == dialog ) {
|
||||
dialog = super.onCreateDialog( id );
|
||||
}
|
||||
|
|
|
@ -74,6 +74,7 @@ public class UpdateCheckReceiver extends BroadcastReceiver {
|
|||
private static final String k_PARAMS = "params";
|
||||
private static final String k_DEVID = "did";
|
||||
private static final String k_XLATEINFO = "xlatinfo";
|
||||
private static final String k_APPGITREV = "apprev";
|
||||
|
||||
@Override
|
||||
public void onReceive( Context context, Intent intent )
|
||||
|
@ -122,6 +123,13 @@ public class UpdateCheckReceiver extends BroadcastReceiver {
|
|||
JSONObject params = new JSONObject();
|
||||
PackageManager pm = context.getPackageManager();
|
||||
String packageName = context.getPackageName();
|
||||
int versionCode;
|
||||
try {
|
||||
versionCode = pm.getPackageInfo( packageName, 0 ).versionCode;
|
||||
} catch ( PackageManager.NameNotFoundException nnfe ) {
|
||||
DbgUtils.loge( nnfe );
|
||||
versionCode = 0;
|
||||
}
|
||||
|
||||
// App update
|
||||
if ( Utils.isGooglePlayApp( context ) ) {
|
||||
|
@ -130,8 +138,6 @@ public class UpdateCheckReceiver extends BroadcastReceiver {
|
|||
String installer = pm.getInstallerPackageName( packageName );
|
||||
|
||||
try {
|
||||
int versionCode = pm.getPackageInfo( packageName, 0 ).versionCode;
|
||||
|
||||
JSONObject appParams = new JSONObject();
|
||||
|
||||
appParams.put( k_NAME, packageName );
|
||||
|
@ -143,8 +149,6 @@ public class UpdateCheckReceiver extends BroadcastReceiver {
|
|||
}
|
||||
params.put( k_APP, appParams );
|
||||
params.put( k_DEVID, XWPrefs.getDevID( context ) );
|
||||
} catch ( PackageManager.NameNotFoundException nnfe ) {
|
||||
DbgUtils.loge( nnfe );
|
||||
} catch ( org.json.JSONException jse ) {
|
||||
DbgUtils.loge( jse );
|
||||
}
|
||||
|
@ -166,7 +170,7 @@ public class UpdateCheckReceiver extends BroadcastReceiver {
|
|||
}
|
||||
|
||||
// Xlations update
|
||||
JSONObject xlationUpdate = LocUtils.makeForXlationUpdate( context );
|
||||
JSONArray xlationUpdate = LocUtils.makeForXlationUpdate( context );
|
||||
if ( null != xlationUpdate ) {
|
||||
try {
|
||||
params.put( k_XLATEINFO, xlationUpdate );
|
||||
|
@ -176,8 +180,16 @@ public class UpdateCheckReceiver extends BroadcastReceiver {
|
|||
}
|
||||
|
||||
if ( 0 < params.length() ) {
|
||||
new UpdateQueryTask( context, params, fromUI, pm,
|
||||
packageName, dals ).execute();
|
||||
try {
|
||||
params.put( k_APPGITREV, BuildConstants.GIT_HASH );
|
||||
params.put( k_NAME, packageName );
|
||||
params.put( k_AVERS, versionCode );
|
||||
DbgUtils.logf( "current update: %s", params.toString() );
|
||||
new UpdateQueryTask( context, params, fromUI, pm,
|
||||
packageName, dals ).execute();
|
||||
} catch ( org.json.JSONException jse ) {
|
||||
DbgUtils.loge( jse );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -387,8 +399,8 @@ public class UpdateCheckReceiver extends BroadcastReceiver {
|
|||
|
||||
// translations info
|
||||
if ( jobj.has( k_XLATEINFO ) ) {
|
||||
JSONObject data = jobj.getJSONObject( k_XLATEINFO );
|
||||
int nAdded = LocUtils.addXlation( m_context, data );
|
||||
JSONArray data = jobj.getJSONArray( k_XLATEINFO );
|
||||
int nAdded = LocUtils.addXlations( m_context, data );
|
||||
if ( 0 < nAdded ) {
|
||||
gotOne = true;
|
||||
String msg = LocUtils.getString( m_context, R.string
|
||||
|
|
|
@ -97,19 +97,39 @@ public class Utils {
|
|||
return s_isFirstBootThisVersion;
|
||||
}
|
||||
|
||||
// Does the device have ability to send SMS -- e.g. is it a phone
|
||||
// and not a Kindle Fire. Not related to XWApp.SMSSUPPORTED
|
||||
public static boolean isGSMPhone( Context context )
|
||||
{
|
||||
boolean result = false;
|
||||
TelephonyManager tm = (TelephonyManager)
|
||||
context.getSystemService( Context.TELEPHONY_SERVICE );
|
||||
if ( null != tm ) {
|
||||
result = TelephonyManager.PHONE_TYPE_GSM == tm.getPhoneType();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Does the device have ability to send SMS -- e.g. is it a phone and not
|
||||
// a Kindle Fire. Not related to XWApp.SMSSUPPORTED. Note that as a
|
||||
// temporary workaround for KitKat having broken use of non-data messages,
|
||||
// we only support SMS on kitkat if data messages have been turned on (and
|
||||
// that's not allowed except on GSM phones.)
|
||||
public static boolean deviceSupportsSMS( Context context )
|
||||
{
|
||||
if ( null == s_deviceSupportSMS ) {
|
||||
boolean doesSMS = false;
|
||||
// TEMPORARY: disable SMS on KITKAT
|
||||
if ( 19 > Integer.valueOf( android.os.Build.VERSION.SDK ) ) {
|
||||
// TEMPORARY: disable SMS on KITKAT UNLESS use-text turned on
|
||||
boolean preKitkat = 19 > Integer.valueOf( android.os.Build.VERSION.SDK);
|
||||
boolean usingData =
|
||||
XWPrefs.getPrefsBoolean( context, R.string.key_send_data_sms,
|
||||
false );
|
||||
if ( preKitkat || usingData ) {
|
||||
TelephonyManager tm = (TelephonyManager)
|
||||
context.getSystemService(Context.TELEPHONY_SERVICE);
|
||||
if ( null != tm ) {
|
||||
int type = tm.getPhoneType();
|
||||
doesSMS = TelephonyManager.PHONE_TYPE_NONE != type;
|
||||
doesSMS = (usingData && !preKitkat)
|
||||
? TelephonyManager.PHONE_TYPE_GSM == type
|
||||
: TelephonyManager.PHONE_TYPE_NONE != type;
|
||||
}
|
||||
}
|
||||
s_deviceSupportSMS = new Boolean( doesSMS );
|
||||
|
|
|
@ -430,7 +430,7 @@ public class XWPrefs {
|
|||
setPrefsString( context, keyID, TextUtils.join( "\n", value ) );
|
||||
}
|
||||
|
||||
public static String getLocale( Context context )
|
||||
public static String getFakeLocale( Context context )
|
||||
{
|
||||
return getPrefsString( context, R.string.key_xlations_locale );
|
||||
}
|
||||
|
|
|
@ -36,12 +36,18 @@ import android.widget.Button;
|
|||
import android.widget.CheckBox;
|
||||
import android.widget.Spinner;
|
||||
import android.widget.TextView;
|
||||
import android.preference.Preference;
|
||||
import android.preference.PreferenceGroup;
|
||||
import android.preference.PreferenceActivity;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
|
@ -55,12 +61,8 @@ import org.eehouse.android.xw4.Utils;
|
|||
import org.eehouse.android.xw4.XWPrefs;
|
||||
|
||||
public class LocUtils {
|
||||
// Keep this in sync with gen_loc_ids.py and what's used in the menu.xml
|
||||
// files to mark me-localized strings.
|
||||
private static final int FMT_LEN = 4;
|
||||
private static final String k_LOCALE = "locale";
|
||||
private static final String k_XLATPROTO = "proto";
|
||||
private static final int XLATE_CUR_VERSION = 1;
|
||||
private static final String k_XLATEVERS = "xlatevers";
|
||||
|
||||
private static Map<String, String> s_xlationsLocal = null;
|
||||
|
@ -121,6 +123,11 @@ public class LocUtils {
|
|||
xlateView( activity, Utils.getContentView( activity ) );
|
||||
}
|
||||
|
||||
public static void xlatePreferences( PreferenceActivity activity )
|
||||
{
|
||||
xlatePreferences( activity, activity.getPreferenceScreen(), 0 );
|
||||
}
|
||||
|
||||
public static void xlateView( Context context, View view )
|
||||
{
|
||||
DbgUtils.logf( "xlateView() top level" );
|
||||
|
@ -132,6 +139,15 @@ public class LocUtils {
|
|||
xlateMenu( activity, menu, 0 );
|
||||
}
|
||||
|
||||
private static String xlateString( Context context, CharSequence str )
|
||||
{
|
||||
String result = null;
|
||||
if ( null != str ) {
|
||||
result = xlateString( context, str.toString() );
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static String xlateString( Context context, String str )
|
||||
{
|
||||
if ( LocIDs.getS_MAP( context ).containsKey( str ) ) {
|
||||
|
@ -225,55 +241,77 @@ public class LocUtils {
|
|||
|
||||
public static void saveLocalData( Context context )
|
||||
{
|
||||
DBUtils.saveXlations( context, s_curLocale, s_xlationsLocal, false );
|
||||
DBUtils.saveXlations( context, getCurLocale( context ),
|
||||
s_xlationsLocal, false );
|
||||
}
|
||||
|
||||
public static JSONObject makeForXlationUpdate( Context context )
|
||||
public static JSONArray makeForXlationUpdate( Context context )
|
||||
{
|
||||
JSONObject result = null;
|
||||
if ( null != s_curLocale && 0 < s_curLocale.length() ) {
|
||||
try {
|
||||
String version = DBUtils.getStringFor( context, k_XLATEVERS, "0" );
|
||||
result = new JSONObject()
|
||||
.put( k_XLATPROTO, XLATE_CUR_VERSION )
|
||||
.put( k_LOCALE, s_curLocale )
|
||||
.put( k_XLATEVERS, version );
|
||||
} catch ( org.json.JSONException jse ) {
|
||||
DbgUtils.loge( jse );
|
||||
}
|
||||
String locale = getCurLocale( context );
|
||||
String fake = XWPrefs.getFakeLocale( context );
|
||||
JSONArray result = new JSONArray()
|
||||
.put( entryForLocale( context, locale ) );
|
||||
if ( null != fake && 0 < fake.length() && ! fake.equals(locale) ) {
|
||||
result.put( entryForLocale( context, fake ) );
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static JSONObject entryForLocale( Context context, String locale )
|
||||
{
|
||||
JSONObject result = null;
|
||||
try {
|
||||
String version =
|
||||
DBUtils.getStringFor( context, localeKey(locale), "0" );
|
||||
result = new JSONObject()
|
||||
.put( k_LOCALE, locale )
|
||||
.put( k_XLATEVERS, version );
|
||||
} catch ( org.json.JSONException jse ) {
|
||||
DbgUtils.loge( jse );
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static String localeKey( String locale )
|
||||
{
|
||||
return String.format( "%s:%s", k_XLATEVERS, locale );
|
||||
}
|
||||
|
||||
private static final String k_OLD = "old";
|
||||
private static final String k_NEW = "new";
|
||||
private static final String k_PAIRS = "pairs";
|
||||
|
||||
public static int addXlation( Context context, JSONObject data )
|
||||
public static int addXlations( Context context, JSONArray data )
|
||||
{
|
||||
int nAdded = 0;
|
||||
try {
|
||||
int newVersion = data.getInt( k_NEW );
|
||||
JSONArray pairs = data.getJSONArray( k_PAIRS );
|
||||
DbgUtils.logf( "got pairs of len %d, version %d", pairs.length(),
|
||||
newVersion );
|
||||
int nLocales = data.length();
|
||||
for ( int ii = 0; ii < nLocales; ++ii ) {
|
||||
JSONObject entry = data.getJSONObject( ii );
|
||||
String locale = entry.getString( k_LOCALE );
|
||||
String newVersion = entry.getString( k_NEW );
|
||||
JSONArray pairs = entry.getJSONArray( k_PAIRS );
|
||||
DbgUtils.logf( "addXlations: locale %s: got pairs of len %d, version %s", locale,
|
||||
pairs.length(), newVersion );
|
||||
|
||||
int len = pairs.length();
|
||||
Map<String,String> newXlations = new HashMap<String,String>( len );
|
||||
for ( int ii = 0; ii < len; ++ii ) {
|
||||
JSONObject pair = pairs.getJSONObject( ii );
|
||||
String key = pair.getString( "en" );
|
||||
String txt = pair.getString( "loc" );
|
||||
newXlations.put( key, txt );
|
||||
int len = pairs.length();
|
||||
Map<String,String> newXlations = new HashMap<String,String>( len );
|
||||
for ( int jj = 0; jj < len; ++jj ) {
|
||||
JSONObject pair = pairs.getJSONObject( jj );
|
||||
int id = pair.getInt( "id" );
|
||||
String key = context.getString( id );
|
||||
Assert.assertNotNull( key );
|
||||
String txt = pair.getString( "loc" );
|
||||
txt = replaceEscaped( txt );
|
||||
newXlations.put( key, txt );
|
||||
}
|
||||
|
||||
DBUtils.saveXlations( context, locale, newXlations, true );
|
||||
DBUtils.setStringFor( context, localeKey(locale), newVersion );
|
||||
nAdded += len;
|
||||
}
|
||||
|
||||
DBUtils.saveXlations( context, s_curLocale, newXlations, true );
|
||||
DBUtils.setStringFor( context, k_XLATEVERS,
|
||||
String.format( "%d", newVersion ) );
|
||||
|
||||
s_xlationsBlessed = null;
|
||||
loadXlations( context );
|
||||
nAdded = len;
|
||||
} catch ( org.json.JSONException jse ) {
|
||||
DbgUtils.loge( jse );
|
||||
}
|
||||
|
@ -334,13 +372,23 @@ public class LocUtils {
|
|||
}
|
||||
}
|
||||
|
||||
private static void loadXlations( Context context )
|
||||
private static String getCurLocale( Context context )
|
||||
{
|
||||
if ( null == s_curLocale ) {
|
||||
s_curLocale = XWPrefs.getLocale( context );
|
||||
String locale = XWPrefs.getFakeLocale( context );
|
||||
if ( null == locale || 0 == locale.length() ) {
|
||||
locale = Locale.getDefault().toString();
|
||||
}
|
||||
s_curLocale = locale;
|
||||
}
|
||||
return s_curLocale;
|
||||
}
|
||||
|
||||
private static void loadXlations( Context context )
|
||||
{
|
||||
if ( null == s_xlationsLocal || null == s_xlationsBlessed ) {
|
||||
Object[] asObjs = DBUtils.getXlations( context, s_curLocale );
|
||||
Object[] asObjs = DBUtils.getXlations( context,
|
||||
getCurLocale( context ) );
|
||||
s_xlationsLocal = (Map<String,String>)asObjs[0];
|
||||
s_xlationsBlessed = (Map<String,String>)asObjs[1];
|
||||
DbgUtils.logf( "loadXlations: got %d local strings, %d blessed strings",
|
||||
|
@ -367,16 +415,6 @@ public class LocUtils {
|
|||
return s_idsToKeys.get( id );
|
||||
}
|
||||
|
||||
private static boolean isEnabled( Context context )
|
||||
{
|
||||
if ( null == s_enabled ) {
|
||||
s_curLocale = XWPrefs.getLocale( context );
|
||||
s_enabled = new Boolean( null != s_curLocale &&
|
||||
0 < s_curLocale.length() );
|
||||
}
|
||||
return s_enabled;
|
||||
}
|
||||
|
||||
private static void xlateView( Context context, View view, int depth )
|
||||
{
|
||||
// DbgUtils.logf( "xlateView(depth=%d, view=%s, canRecurse=%b)", depth,
|
||||
|
@ -388,11 +426,6 @@ public class LocUtils {
|
|||
} else if ( view instanceof TextView ) {
|
||||
TextView tv = (TextView)view;
|
||||
tv.setText( xlateString( context, tv.getText().toString() ) );
|
||||
// } else if ( view instanceof CheckBox ) {
|
||||
// CheckBox box = (CheckBox)view;
|
||||
// String str = box.getText().toString();
|
||||
// str = xlateString( context, str );
|
||||
// box.setText( str );
|
||||
} else if ( view instanceof Spinner ) {
|
||||
Spinner sp = (Spinner)view;
|
||||
CharSequence prompt = sp.getPrompt();
|
||||
|
@ -415,23 +448,80 @@ public class LocUtils {
|
|||
}
|
||||
}
|
||||
|
||||
public static void xlatePreferences( Context context, Preference pref,
|
||||
int depth )
|
||||
{
|
||||
// DbgUtils.logf( "xlatePreferences(depth=%d, view=%s, canRecurse=%b)", depth,
|
||||
// pref.getClass().getName(), pref instanceof PreferenceGroup );
|
||||
|
||||
String str = xlateString( context, pref.getSummary() );
|
||||
if ( null != str ) {
|
||||
pref.setSummary( str );
|
||||
}
|
||||
str = xlateString( context, pref.getTitle() );
|
||||
if ( null != str ) {
|
||||
pref.setTitle( str );
|
||||
}
|
||||
|
||||
if ( pref instanceof PreferenceGroup ) {
|
||||
PreferenceGroup group = (PreferenceGroup)pref;
|
||||
int count = group.getPreferenceCount();
|
||||
for ( int ii = 0; ii < count; ++ii ) {
|
||||
xlatePreferences( context, group.getPreference(ii), 1 + depth );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This is for testing, but the ability to pull the formatters will be
|
||||
// critical for validating local transations of strings containing
|
||||
// formatters.
|
||||
private static String toUpperCase( String str )
|
||||
{
|
||||
String[] parts = str.split( "%[\\d]\\$[ds]" );
|
||||
StringBuilder sb = new StringBuilder();
|
||||
int offset = 0;
|
||||
for ( String part : parts ) {
|
||||
sb.append( part.toUpperCase() );
|
||||
offset += part.length();
|
||||
if ( offset < str.length() ) {
|
||||
sb.append( str.substring( offset, offset + FMT_LEN ) );
|
||||
offset += FMT_LEN;
|
||||
String result = null;
|
||||
if ( UPPER_CASE ) {
|
||||
String[] parts = str.split( "%[\\d]\\$[ds]" );
|
||||
StringBuilder sb = new StringBuilder();
|
||||
int offset = 0;
|
||||
for ( String part : parts ) {
|
||||
sb.append( part.toUpperCase() );
|
||||
offset += part.length();
|
||||
if ( offset < str.length() ) {
|
||||
sb.append( str.substring( offset, offset + FMT_LEN ) );
|
||||
offset += FMT_LEN;
|
||||
}
|
||||
}
|
||||
result = sb.toString();
|
||||
}
|
||||
return sb.toString();
|
||||
return result;
|
||||
}
|
||||
|
||||
private static Pattern s_patUnicode = Pattern.compile("(\\\\[Uu][0-9a-fA-F]{4})");
|
||||
private static Pattern s_patCr = Pattern.compile("\\\\n");
|
||||
|
||||
private static String replaceEscaped( String txt )
|
||||
{
|
||||
// String orig = txt;
|
||||
|
||||
// Swap unicode escapes for real chars
|
||||
Matcher matcher = s_patUnicode.matcher( txt );
|
||||
StringBuffer sb = new StringBuffer();
|
||||
while ( matcher.find() ) {
|
||||
int start = matcher.start();
|
||||
int end = matcher.end();
|
||||
String match = txt.substring( start, end );
|
||||
char ch = (char)Integer.parseInt( match.substring(2), 16 );
|
||||
matcher.appendReplacement( sb, String.valueOf(ch) );
|
||||
}
|
||||
matcher.appendTail(sb);
|
||||
txt = sb.toString();
|
||||
|
||||
// Swap in real carriage returns
|
||||
txt = s_patCr.matcher( txt ).replaceAll( "\n" );
|
||||
|
||||
// if ( ! orig.equals( txt ) ) {
|
||||
// DbgUtils.logf( "replaceEscaped: <<%s>> -> <<%s>>", orig, txt );
|
||||
// }
|
||||
return txt;
|
||||
}
|
||||
|
||||
public static AlertDialog.Builder makeAlertBuilder( Context context )
|
||||
|
|
|
@ -77,7 +77,7 @@ for PACK_UNSIGNED in $FILES; do
|
|||
|
||||
if [ -n "$XW_RELEASE_SCP_DEST" ]; then
|
||||
echo "running scp ${TARGET} $XW_RELEASE_SCP_DEST"
|
||||
scp "${TARGET}" "$XW_RELEASE_SCP_DEST"
|
||||
scp "${TARGET}" "$XW_RELEASE_SCP_DEST" || true
|
||||
else
|
||||
echo "XW_RELEASE_SCP_DEST not set; you're on your own"
|
||||
fi
|
||||
|
|
|
@ -2,6 +2,13 @@
|
|||
|
||||
set -e -u
|
||||
|
||||
usage() {
|
||||
echo "usage: $0 <variant> <relay_vers> <chatSupported> <thumbSupported>"
|
||||
exit 1
|
||||
}
|
||||
|
||||
[ $# -eq 4 ] || usage
|
||||
|
||||
VARIANT=$1
|
||||
CLIENT_VERS_RELAY=$2
|
||||
CHAT_SUPPORTED=$3
|
||||
|
@ -12,6 +19,11 @@ cd $(dirname $0)
|
|||
cd ../
|
||||
|
||||
GITVERSION=$(../scripts/gitversion.sh)
|
||||
if git rev-parse $GITVERSION 2>/dev/null 1>/dev/null; then
|
||||
GIT_HASH=$(git rev-parse $GITVERSION)
|
||||
else
|
||||
GIT_HASH=unknown
|
||||
fi
|
||||
|
||||
# TODO: deal with case where there's no hash available -- exported
|
||||
# code maybe? Better: gitversion.sh does that.
|
||||
|
@ -34,6 +46,7 @@ cat <<EOF > ${BUILD_DIR}/src/org/eehouse/android/${VARIANT}/BuildConstants.java
|
|||
package org.eehouse.android.${VARIANT};
|
||||
class BuildConstants {
|
||||
public static final String GIT_REV = "$SHORTVERS";
|
||||
public static final String GIT_HASH = "$GIT_HASH";
|
||||
public static final short CLIENT_VERS_RELAY = $CLIENT_VERS_RELAY;
|
||||
public static final boolean CHAT_SUPPORTED = $CHAT_SUPPORTED;
|
||||
public static final boolean THUMBNAIL_SUPPORTED = $THUMBNAIL_SUPPORTED;
|
||||
|
|
|
@ -38,6 +38,7 @@ SRC = \
|
|||
udpager.cpp \
|
||||
udpqueue.cpp \
|
||||
xwrelay.cpp \
|
||||
querybld.cpp \
|
||||
|
||||
# STATIC ?= -static
|
||||
GITINFO = gitversion.txt
|
||||
|
|
|
@ -43,11 +43,8 @@
|
|||
|
||||
static DBMgr* s_instance = NULL;
|
||||
|
||||
#define DELIM "\1"
|
||||
#define MAX_NUM_PLAYERS 4
|
||||
|
||||
static void formatParams( char* paramValues[], int nParams, const char* fmt,
|
||||
char* buf, int bufLen, ... );
|
||||
static int here_less_seed( const char* seeds, int perDeviceSum,
|
||||
unsigned short seed );
|
||||
static void destr_function( void* conn );
|
||||
|
@ -105,20 +102,21 @@ DBMgr::AddNew( const char* cookie, const char* connName, CookieID cid,
|
|||
if ( !cookie ) cookie = "";
|
||||
if ( !connName ) connName = "";
|
||||
|
||||
const char* command = "INSERT INTO " GAMES_TABLE
|
||||
" (cid, room, connName, nTotal, lang, pub)"
|
||||
" VALUES( $1, $2, $3, $4, $5, $6 )";
|
||||
int nParams = 6;
|
||||
char* paramValues[nParams];
|
||||
char buf[512];
|
||||
formatParams( paramValues, nParams,
|
||||
"%d"DELIM"%s"DELIM"%s"DELIM"%d"DELIM"%d"DELIM"%s",
|
||||
buf, sizeof(buf), cid, cookie, connName, nPlayersT,
|
||||
langCode, isPublic?"TRUE":"FALSE" );
|
||||
QueryBuilder qb;
|
||||
qb.appendQueryf( "INSERT INTO " GAMES_TABLE
|
||||
" (cid, room, connName, nTotal, lang, pub)"
|
||||
" VALUES( $$, $$, $$, $$, $$, $$ )" )
|
||||
.appendParam(cid)
|
||||
.appendParam(cookie)
|
||||
.appendParam(connName)
|
||||
.appendParam(nPlayersT)
|
||||
.appendParam(langCode)
|
||||
.appendParam(isPublic?"TRUE":"FALSE" )
|
||||
.finish();
|
||||
|
||||
PGresult* result = PQexecParams( getThreadConn(), command,
|
||||
nParams, NULL,
|
||||
paramValues,
|
||||
PGresult* result = PQexecParams( getThreadConn(), qb.c_str(),
|
||||
qb.paramCount(), NULL,
|
||||
qb.paramValues(),
|
||||
NULL, NULL, 0 );
|
||||
if ( PGRES_COMMAND_OK != PQresultStatus(result) ) {
|
||||
logf( XW_LOGERROR, "PQexec=>%s;%s", PQresStatus(PQresultStatus(result)),
|
||||
|
@ -271,28 +269,27 @@ DBMgr::SeenSeed( const char* cookie, unsigned short seed,
|
|||
char* connNameBuf, int bufLen, int* nPlayersHP,
|
||||
CookieID* cid )
|
||||
{
|
||||
int nParams = 5;
|
||||
char* paramValues[nParams];
|
||||
char buf[512];
|
||||
formatParams( paramValues, nParams,
|
||||
"%s"DELIM"%d"DELIM"%d"DELIM"%d"DELIM"%s", buf, sizeof(buf),
|
||||
cookie, langCode, nPlayersT, seed,
|
||||
wantsPublic?"TRUE":"FALSE" );
|
||||
QueryBuilder qb;
|
||||
qb.appendQueryf( "SELECT cid, connName, seeds, sum_array(nPerDevice) FROM "
|
||||
GAMES_TABLE
|
||||
" WHERE NOT dead"
|
||||
" AND room ILIKE $$"
|
||||
" AND lang = $$"
|
||||
" AND nTotal = $$"
|
||||
" AND $$ = ANY(seeds)"
|
||||
" AND $$ = pub"
|
||||
" ORDER BY ctime DESC"
|
||||
" LIMIT 1")
|
||||
.appendParam(cookie)
|
||||
.appendParam(langCode)
|
||||
.appendParam(nPlayersT)
|
||||
.appendParam(seed)
|
||||
.appendParam(wantsPublic?"TRUE":"FALSE" )
|
||||
.finish();
|
||||
|
||||
const char* cmd = "SELECT cid, connName, seeds, sum_array(nPerDevice) FROM "
|
||||
GAMES_TABLE
|
||||
" WHERE NOT dead"
|
||||
" AND room ILIKE $1"
|
||||
" AND lang = $2"
|
||||
" AND nTotal = $3"
|
||||
" AND $4 = ANY(seeds)"
|
||||
" AND $5 = pub"
|
||||
" ORDER BY ctime DESC"
|
||||
" LIMIT 1";
|
||||
|
||||
PGresult* result = PQexecParams( getThreadConn(), cmd,
|
||||
nParams, NULL,
|
||||
paramValues,
|
||||
PGresult* result = PQexecParams( getThreadConn(), qb.c_str(),
|
||||
qb.paramCount(), NULL,
|
||||
qb.paramValues(),
|
||||
NULL, NULL, 0 );
|
||||
bool found = 1 == PQntuples( result );
|
||||
if ( found ) {
|
||||
|
@ -312,31 +309,28 @@ DBMgr::FindOpen( const char* cookie, int lang, int nPlayersT, int nPlayersH,
|
|||
bool wantsPublic, char* connNameBuf, int bufLen,
|
||||
int* nPlayersHP )
|
||||
{
|
||||
CookieID cid = 0;
|
||||
QueryBuilder qb;
|
||||
qb.appendQueryf("SELECT cid, connName, sum_array(nPerDevice) FROM "
|
||||
GAMES_TABLE
|
||||
" WHERE NOT dead"
|
||||
" AND room ILIKE $$"
|
||||
" AND lang = $$"
|
||||
" AND nTotal = $$"
|
||||
" AND $$ <= nTotal-sum_array(nPerDevice)"
|
||||
" AND $$ = pub"
|
||||
" LIMIT 1")
|
||||
.appendParam(cookie)
|
||||
.appendParam(lang)
|
||||
.appendParam(nPlayersT)
|
||||
.appendParam(nPlayersH)
|
||||
.appendParam(wantsPublic?"TRUE":"FALSE" )
|
||||
.finish();
|
||||
|
||||
int nParams = 5;
|
||||
char* paramValues[nParams];
|
||||
char buf[512];
|
||||
formatParams( paramValues, nParams,
|
||||
"%s"DELIM"%d"DELIM"%d"DELIM"%d"DELIM"%s", buf, sizeof(buf),
|
||||
cookie, lang, nPlayersT, nPlayersH, wantsPublic?"TRUE":"FALSE" );
|
||||
|
||||
/* NOTE: ILIKE, for case-insensitive comparison, is a postgres extension
|
||||
to SQL. */
|
||||
const char* cmd = "SELECT cid, connName, sum_array(nPerDevice) FROM "
|
||||
GAMES_TABLE
|
||||
" WHERE NOT dead"
|
||||
" AND room ILIKE $1"
|
||||
" AND lang = $2"
|
||||
" AND nTotal = $3"
|
||||
" AND $4 <= nTotal-sum_array(nPerDevice)"
|
||||
" AND $5 = pub"
|
||||
" LIMIT 1";
|
||||
|
||||
PGresult* result = PQexecParams( getThreadConn(), cmd,
|
||||
nParams, NULL,
|
||||
paramValues,
|
||||
PGresult* result = PQexecParams( getThreadConn(), qb.c_str(),
|
||||
qb.paramCount(), NULL,
|
||||
qb.paramValues(),
|
||||
NULL, NULL, 0 );
|
||||
CookieID cid = 0;
|
||||
if ( 1 == PQntuples( result ) ) {
|
||||
cid = atoi( PQgetvalue( result, 0, 0 ) );
|
||||
snprintf( connNameBuf, bufLen, "%s", PQgetvalue( result, 0, 1 ) );
|
||||
|
@ -396,14 +390,21 @@ DBMgr::RegisterDevice( const DevID* host, int clientVersion,
|
|||
devID = (DevIDRelay)random();
|
||||
} while ( DEVID_NONE == devID );
|
||||
|
||||
StrWPF query;
|
||||
query.catf( "INSERT INTO " DEVICES_TABLE " (id, devTypes[1],"
|
||||
" devids[1], clntVers, versdesc, model, osvers)"
|
||||
" VALUES( %d, %d, '%s', %d, '%s', '%s', '%s' )",
|
||||
devID, host->m_devIDType, devidStr, clientVersion,
|
||||
desc, model, osVers );
|
||||
logf( XW_LOGINFO, "%s: %s", __func__, query.c_str() );
|
||||
success = execSql( query );
|
||||
QueryBuilder qb;
|
||||
qb.appendQueryf( "INSERT INTO " DEVICES_TABLE " (id, devTypes[1],"
|
||||
" devids[1], clntVers, versdesc, model, osvers)"
|
||||
" VALUES($$, $$, $$, $$, $$, $$, $$)" );
|
||||
|
||||
qb.appendParam( devID )
|
||||
.appendParam( host->m_devIDType )
|
||||
.appendParam( devidStr )
|
||||
.appendParam( clientVersion )
|
||||
.appendParam( desc )
|
||||
.appendParam( model )
|
||||
.appendParam( osVers )
|
||||
.finish();
|
||||
|
||||
success = execParams( qb );
|
||||
}
|
||||
}
|
||||
return devID;
|
||||
|
@ -420,15 +421,17 @@ DBMgr::ReregisterDevice( DevIDRelay relayID, const DevID* host,
|
|||
const char* const desc, int clientVersion,
|
||||
const char* const model, const char* const osVers )
|
||||
{
|
||||
// First update the existing
|
||||
StrWPF query;
|
||||
query.catf( "UPDATE " DEVICES_TABLE " SET "
|
||||
"devTypes = array_prepend( %d, devTypes), "
|
||||
"devids = array_prepend('%s', devids), ",
|
||||
host->m_devIDType, host->m_devIDString.c_str() );
|
||||
QueryBuilder qb;
|
||||
qb.appendQueryf( "UPDATE " DEVICES_TABLE " SET "
|
||||
"devTypes = array_prepend($$, devTypes), "
|
||||
"devids = array_prepend($$, devids), " )
|
||||
|
||||
formatUpdate( query, true, desc, clientVersion, model, osVers, relayID );
|
||||
execSql( query );
|
||||
.appendParam( host->m_devIDType )
|
||||
.appendParam( host->m_devIDString.c_str() );
|
||||
|
||||
formatUpdate( qb, true, desc, clientVersion, model, osVers, relayID );
|
||||
qb.finish();
|
||||
execParams( qb );
|
||||
}
|
||||
|
||||
// Return true if the relayID exists in the DB already
|
||||
|
@ -445,10 +448,11 @@ DBMgr::UpdateDevice( DevIDRelay relayID, const char* const desc,
|
|||
}
|
||||
|
||||
if ( exists ) {
|
||||
StrWPF query;
|
||||
query.catf( "UPDATE " DEVICES_TABLE " SET " );
|
||||
formatUpdate( query, false, desc, clientVersion, model, osVers, relayID );
|
||||
execSql( query );
|
||||
QueryBuilder qb;
|
||||
qb.appendQueryf( "UPDATE " DEVICES_TABLE " SET " );
|
||||
formatUpdate( qb, false, desc, clientVersion, model, osVers, relayID );
|
||||
qb.finish();
|
||||
execParams( qb );
|
||||
}
|
||||
return exists;
|
||||
}
|
||||
|
@ -460,26 +464,33 @@ DBMgr::UpdateDevice( DevIDRelay relayID )
|
|||
}
|
||||
|
||||
void
|
||||
DBMgr::formatUpdate( StrWPF& query, bool append, const char* const desc,
|
||||
DBMgr::formatUpdate( QueryBuilder& qb,
|
||||
bool append, const char* const desc,
|
||||
int clientVersion, const char* const model,
|
||||
const char* const osVers, DevIDRelay relayID )
|
||||
{
|
||||
if ( append ) {
|
||||
query.catf( "mtimes=array_prepend('now', mtimes)" ); // FIXME: too many
|
||||
qb.appendQueryf( "mtimes=array_prepend('now', mtimes)" ); // FIXME: too many
|
||||
} else {
|
||||
query.catf( "mtimes[1]='now'" );
|
||||
qb.appendQueryf( "mtimes[1]='now'" );
|
||||
}
|
||||
|
||||
if ( NULL != desc && '\0' != desc[0] ) {
|
||||
query.catf( ", clntVers=%d, versDesc='%s'", clientVersion, desc );
|
||||
qb.appendQueryf( ", clntVers=$$" )
|
||||
.appendParam( clientVersion )
|
||||
.appendQueryf( ", versDesc=$$" )
|
||||
.appendParam( desc );
|
||||
}
|
||||
if ( NULL != model && '\0' != model[0] ) {
|
||||
query.catf( ", model='%s'", model );
|
||||
qb.appendQueryf( ", model=$$" )
|
||||
.appendParam( model );
|
||||
}
|
||||
if ( NULL != osVers && '\0' != osVers[0] ) {
|
||||
query.catf( ", osvers='%s'", osVers );
|
||||
qb.appendQueryf( ", osvers=$$" )
|
||||
.appendParam( osVers );
|
||||
}
|
||||
query.catf( " WHERE id = %d", relayID );
|
||||
qb.appendQueryf( " WHERE id = $$" )
|
||||
.appendParam( relayID );
|
||||
}
|
||||
|
||||
HostID
|
||||
|
@ -831,6 +842,22 @@ DBMgr::execSql( const char* const query )
|
|||
return ok;
|
||||
}
|
||||
|
||||
bool
|
||||
DBMgr::execParams( QueryBuilder& qb )
|
||||
{
|
||||
PGresult* result = PQexecParams( getThreadConn(), qb.c_str(),
|
||||
qb.paramCount(), NULL,
|
||||
qb.paramValues(),
|
||||
NULL, NULL, 0 );
|
||||
bool success = PGRES_COMMAND_OK == PQresultStatus( result );
|
||||
if ( !success ) {
|
||||
logf( XW_LOGERROR, "PQexecParams(%s)=>%s;%s", qb.c_str(),
|
||||
PQresStatus(PQresultStatus(result)),
|
||||
PQresultErrorMessage(result) );
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
void
|
||||
DBMgr::readArray( const char* const connName, const char* column, int arr[] ) /* len 4 */
|
||||
{
|
||||
|
@ -1255,31 +1282,6 @@ void DBMgr::clearHasNoMessages( DevIDRelay devid )
|
|||
assert( !hasNoMessages( devid ) );
|
||||
}
|
||||
|
||||
static void
|
||||
formatParams( char* paramValues[], int nParams, const char* fmt, char* buf,
|
||||
int bufLen, ... )
|
||||
{
|
||||
va_list ap;
|
||||
va_start( ap, bufLen );
|
||||
|
||||
int len = vsnprintf( buf, bufLen, fmt, ap );
|
||||
assert( buf[len] == '\0' );
|
||||
|
||||
int pnum;
|
||||
char* ptr = buf;
|
||||
for ( pnum = 0; pnum < nParams; ++pnum ) {
|
||||
paramValues[pnum] = ptr;
|
||||
for ( ; *ptr != '\0' && *ptr != DELIM[0]; ++ptr ) {
|
||||
// do nothing
|
||||
assert( ptr < &buf[bufLen] );
|
||||
}
|
||||
// we've found an end
|
||||
*ptr = '\0';
|
||||
++ptr;
|
||||
}
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
static int
|
||||
here_less_seed( const char* seeds, int sumPerDevice, unsigned short seed )
|
||||
{
|
||||
|
|
|
@ -32,6 +32,7 @@
|
|||
#include "xwrelay_priv.h"
|
||||
#include "devid.h"
|
||||
#include "strwpf.h"
|
||||
#include "querybld.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
|
@ -149,6 +150,7 @@ class DBMgr {
|
|||
DBMgr();
|
||||
bool execSql( const string& query );
|
||||
bool execSql( const char* const query ); /* no-results query */
|
||||
bool execParams( QueryBuilder& qb );
|
||||
void readArray( const char* const connName, const char* column, int arr[] );
|
||||
DevIDRelay getDevID( const char* connName, int hid );
|
||||
DevIDRelay getDevID( const DevID* devID );
|
||||
|
@ -160,7 +162,7 @@ class DBMgr {
|
|||
bool nullConnnameOK );
|
||||
int CountStoredMessages( const char* const connName, int hid );
|
||||
bool UpdateDevice( DevIDRelay relayID );
|
||||
void formatUpdate( StrWPF& query, bool append, const char* const desc,
|
||||
void formatUpdate( QueryBuilder& qb, bool append, const char* const desc,
|
||||
int clientVersion, const char* const model,
|
||||
const char* const osVers, DevIDRelay relayID );
|
||||
|
||||
|
|
78
xwords4/relay/querybld.cpp
Normal file
78
xwords4/relay/querybld.cpp
Normal file
|
@ -0,0 +1,78 @@
|
|||
/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */
|
||||
/*
|
||||
* Copyright 2014 by Eric House (xwords@eehouse.org). All rights reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
*/
|
||||
#include <stdarg.h>
|
||||
|
||||
#include "querybld.h"
|
||||
#include "xwrelay_priv.h"
|
||||
|
||||
QueryBuilder&
|
||||
QueryBuilder::appendQueryf( const char* fmt, ... )
|
||||
{
|
||||
bool done;
|
||||
do {
|
||||
va_list ap;
|
||||
va_start( ap, fmt );
|
||||
done = m_query.catf( fmt, ap );
|
||||
va_end( ap );
|
||||
} while ( !done );
|
||||
return *this;
|
||||
}
|
||||
|
||||
QueryBuilder&
|
||||
QueryBuilder::appendParam( const char* value )
|
||||
{
|
||||
m_paramIndices.push_back( m_paramBuf.size() );
|
||||
m_paramBuf.catf( "%s%c", value, '\0' );
|
||||
return *this;
|
||||
}
|
||||
|
||||
QueryBuilder&
|
||||
QueryBuilder::appendParam( int value )
|
||||
{
|
||||
m_paramIndices.push_back( m_paramBuf.size() );
|
||||
m_paramBuf.catf( "%d%c", value, '\0' );
|
||||
return *this;
|
||||
}
|
||||
|
||||
/* When done adding params, some of which contain $$, turn these into an order
|
||||
* progression of $1, $2 .. $9. Note assumption that we don't go above 9 since
|
||||
*/
|
||||
void
|
||||
QueryBuilder::finish()
|
||||
{
|
||||
assert( 0 == m_paramValues.size() );
|
||||
|
||||
size_t ii;
|
||||
const char* base = m_paramBuf.c_str();
|
||||
for ( ii = 0; ii < m_paramIndices.size(); ++ii ) {
|
||||
const char* ptr = m_paramIndices[ii] + base;
|
||||
m_paramValues.push_back( ptr );
|
||||
}
|
||||
|
||||
for ( size_t count = 0; ; ++count ) {
|
||||
const char* str = m_query.c_str();
|
||||
const char* ptr = strstr( str, "$$" );
|
||||
if ( !ptr ) {
|
||||
assert( count == m_paramIndices.size() );
|
||||
break;
|
||||
}
|
||||
assert( count < 9 );
|
||||
m_query[1 + ptr - str] = '1' + count;
|
||||
}
|
||||
}
|
47
xwords4/relay/querybld.h
Normal file
47
xwords4/relay/querybld.h
Normal file
|
@ -0,0 +1,47 @@
|
|||
/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */
|
||||
/*
|
||||
* Copyright 2014 by Eric House (xwords@eehouse.org). All rights reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
*/
|
||||
|
||||
#ifndef _QUERYBLD_H_
|
||||
#define _QUERYBLD_H_
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "strwpf.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
class QueryBuilder {
|
||||
|
||||
public:
|
||||
QueryBuilder& appendQueryf( const char* fmt, ... );
|
||||
QueryBuilder& appendParam( const char* value );
|
||||
QueryBuilder& appendParam( int value );
|
||||
void finish();
|
||||
int paramCount() const { return m_paramValues.size(); }
|
||||
const char* const* paramValues() const { return &m_paramValues[0]; }
|
||||
const char* const c_str() const { return m_query.c_str(); }
|
||||
|
||||
private:
|
||||
StrWPF m_query;
|
||||
StrWPF m_paramBuf;
|
||||
vector<size_t> m_paramIndices;
|
||||
vector<const char*> m_paramValues;
|
||||
};
|
||||
|
||||
#endif
|
|
@ -27,26 +27,37 @@
|
|||
/* From stack overflow: snprintf with an expanding buffer.
|
||||
*/
|
||||
|
||||
bool
|
||||
StrWPF::catf( const char* fmt, va_list ap )
|
||||
{
|
||||
bool success = false;
|
||||
const int origsiz = size();
|
||||
resize( origsiz + m_addsiz );
|
||||
|
||||
int len = vsnprintf( (char*)c_str() + origsiz, m_addsiz, fmt, ap );
|
||||
|
||||
if ( len >= m_addsiz ) { // needs more space
|
||||
m_addsiz = len + 1;
|
||||
resize( origsiz );
|
||||
} else if ( -1 == len ) {
|
||||
assert(0); // should be impossible
|
||||
} else {
|
||||
resize( origsiz + len );
|
||||
m_addsiz = 100;
|
||||
success = true;
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
void
|
||||
StrWPF::catf( const char* fmt, ... )
|
||||
{
|
||||
const int origsiz = size();
|
||||
int addsiz = 100;
|
||||
va_list ap;
|
||||
for ( ; ; ) {
|
||||
resize( origsiz + addsiz );
|
||||
|
||||
bool done;
|
||||
do {
|
||||
va_list ap;
|
||||
va_start( ap, fmt );
|
||||
int len = vsnprintf( (char *)c_str() + origsiz, addsiz, fmt, ap );
|
||||
done = catf( fmt, ap );
|
||||
va_end( ap );
|
||||
|
||||
if ( len >= addsiz ) { // needs more space
|
||||
addsiz = len + 1;
|
||||
} else if ( -1 == len ) {
|
||||
assert(0); // should be impossible
|
||||
} else {
|
||||
resize( origsiz + len );
|
||||
break;
|
||||
}
|
||||
}
|
||||
} while ( !done );
|
||||
}
|
||||
|
|
|
@ -21,10 +21,16 @@
|
|||
#define _STRWPF_H_
|
||||
|
||||
#include <string>
|
||||
#include <stdarg.h>
|
||||
|
||||
class StrWPF : public std::string {
|
||||
class StrWPF : public std::string {
|
||||
public:
|
||||
StrWPF() : m_addsiz(100){}
|
||||
|
||||
void catf( const char* fmt, ... );
|
||||
bool catf( const char* fmt, va_list ap );
|
||||
private:
|
||||
int m_addsiz;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
Loading…
Reference in a new issue