Merge branch 'android_branch' into android_translate

This commit is contained in:
Eric House 2019-01-09 19:03:21 -08:00
commit 1625ec9062
116 changed files with 2778 additions and 1675 deletions

View file

@ -1,8 +1,10 @@
git:
depth: false
language: android
jdk: oraclejdk8
env:
global:
- ANDROID_TARGET=android-15
- ANDROID_TARGET=android-14
- ANDROID_ABI=armeabi-v7a,x86
- secure: d8PwteM+xp1IRU3QkvmHtxh+1Ta9n/kl/SJ3EZa3iColVVXY1etzjY3cKrEGKKMJuI4be30kPzvNw9/BVTawDpnU9/NtWqykJ8QHXNWnZIvUQ/kxHBS1DbcstmcYU9gvR83EFb8BT+Y9frpNfMcZDlSvBpEGqDQEPmxiDzSmjdUmJJQWStncxL9pE+lCdM6lHBgtfYoMMiqCQF/DxkQisjyUVF4mbTGuT9JOOWjVsTGPA7ehzsWDHoJ3p2ai8UKHAYucUWZcTt4rkq9l35ExvgKd3L8luk8U3X3Fk9yzVhPJC56T0XNbNrsQ2W7/7oGRv6EQFV3aKDZimJ7CVjBcEjZmPxeUVvCsMW8XB41ZvYcy6xsjF96oyjn1gb0r/2mZbTaWP0izSTwMYZ5vFNKUamDtRZgrneD0lfvXgfTzirrCU7FqO2RH7ZK5PQpSgSoZxKsKyeyFPEa2ihivc95rz1MS6mamle9wrIlSAgEGcaZMIYvKiOnCLk7CZCKuwm2dhYPgzCHW3PUopay59BBwMsSqWpxsiHEr5jYGpb0pHGbzPTJNUpg1LNQX5eMQOMlEt7rfpoC7JG24hR9vxl4Yf9LhxYlSwUiPy7TYHdbA0kUS68skfzxU6+ekWZF2QFM+L4vWCYmEHDy7n+I0df+PavycgNW989ROlAKhQjtMyqM=
android:
@ -10,7 +12,7 @@ android:
- tools
- platform-tools
- build-tools-27.0.3
- android-23
- android-26
licenses:
- android-sdk-preview-license-.+
- android-sdk-license-.+

View file

@ -1,6 +1,6 @@
def INITIAL_CLIENT_VERS = 8
def VERSION_CODE_BASE = 135
def VERSION_NAME = '4.4.139'
def VERSION_CODE_BASE = 136
def VERSION_NAME = '4.4.140'
def FABRIC_API_KEY = System.getenv("FABRIC_API_KEY")
def GCM_SENDER_ID = System.getenv("GCM_SENDER_ID")
def BUILD_INFO_NAME = "build-info.txt"
@ -23,8 +23,8 @@ if ( FABRIC_API_KEY && hasProperty('useCrashlytics') ) {
apply plugin: 'io.fabric'
}
repositories {
maven { url 'https://maven.fabric.io/public' }
google()
maven { url 'https://maven.fabric.io/public' }
}
android {
@ -32,8 +32,8 @@ android {
// default changes and .travis.yml can be kept in sync
buildToolsVersion '27.0.3'
defaultConfig {
minSdkVersion 8
targetSdkVersion 23
minSdkVersion 14
targetSdkVersion 26
versionCode VERSION_CODE_BASE
versionName VERSION_NAME
}
@ -63,6 +63,7 @@ android {
productFlavors {
all {
buildConfigField "String", "BUILD_INFO_NAME", "\"${BUILD_INFO_NAME}\""
resValue "string", "invite_prefix", "/and/"
}
xw4 {
@ -71,7 +72,6 @@ android {
manifestPlaceholders = [ APP_ID: applicationId ]
resValue "string", "app_name", "CrossWords"
resValue "string", "nbs_port", "3344"
resValue "string", "invite_prefix", "/and/"
buildConfigField "boolean", "WIDIR_ENABLED", "false"
buildConfigField "boolean", "RELAYINVITE_SUPPORTED", "false"
@ -84,7 +84,6 @@ android {
manifestPlaceholders = [ APP_ID: applicationId ]
resValue "string", "app_name", "CrossWords"
resValue "string", "nbs_port", "3344"
resValue "string", "invite_prefix", "/and/"
buildConfigField "boolean", "WIDIR_ENABLED", "false"
buildConfigField "boolean", "RELAYINVITE_SUPPORTED", "false"
@ -96,7 +95,6 @@ android {
manifestPlaceholders = [ FABRIC_API_KEY: "$FABRIC_API_KEY", APP_ID: applicationId, ]
resValue "string", "app_name", "CrossDbg"
resValue "string", "nbs_port", "3345"
resValue "string", "invite_prefix", "/anddbg/"
buildConfigField "boolean", "WIDIR_ENABLED", "true"
buildConfigField "boolean", "RELAYINVITE_SUPPORTED", "true"
@ -197,11 +195,19 @@ android {
}
}
ext {
SUPPORT_LIB_VERSION = '27.1.1'
}
dependencies {
// Look into replacing this with a fetch too PENDING
xw4Implementation files('../libs/gcm.jar')
implementation 'com.android.support:support-v4:23.4.0'
implementation "com.android.support:support-v4:$SUPPORT_LIB_VERSION"
implementation "com.android.support:support-compat:$SUPPORT_LIB_VERSION"
implementation "android.arch.lifecycle:extensions:1.1.1"
annotationProcessor "android.arch.lifecycle:compiler:1.1.1"
// 2.6.8 is probably as far forward as I can go without upping my
// min-supported SDK version
@ -222,7 +228,13 @@ task copyStrings(type: Exec) {
task ndkSetup(type: Exec) {
workingDir '../'
commandLine "./scripts/ndksetup.sh", "--with-clang", "--arm-only"
// remove ', "--arm-only"' for Genymotion builds
// I'm putting ARM back for a while. It's too much trouble having
// builds, including those built by travis, that don't run on the
// emulator. Maybe remove this change before each release?
commandLine "./scripts/ndksetup.sh", "--with-clang"
// commandLine "./scripts/ndksetup.sh", "--with-clang", "--arm-only"
}
task myPreBuild(dependsOn: ['ndkSetup', 'mkImages', 'copyStrings', 'mkXml']) {

View file

@ -24,6 +24,8 @@
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.READ_CONTACTS" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<!-- Required for wifi-direct -->
<!-- <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/> -->
@ -113,6 +115,7 @@
<receiver android:name="OnBootReceiver">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"/>
<action android:name="android.intent.action.MY_PACKAGE_REPLACED" />
</intent-filter>
</receiver>
<receiver android:name="RelayReceiver"/>
@ -179,9 +182,17 @@
<activity android:name=".loc.LocItemEditActivity"
/>
<service android:name="RelayService"/>
<service android:name="BTService"/>
<service android:name="SMSService"/>
<service android:name="BTService"
android:permission="android.permission.BIND_JOB_SERVICE"
android:exported="false"
/>
<service android:name="SMSService"
android:exported="false"
/>
<service android:name="RelayService"
android:exported="false"
android:permission="android.permission.BIND_JOB_SERVICE"
/>
<receiver android:name=".MountEventReceiver">
<intent-filter>
@ -201,6 +212,9 @@
<intent-filter>
<action android:name="android.bluetooth.adapter.action.STATE_CHANGED" />
</intent-filter>
<intent-filter>
<action android:name="org.eehouse.android.ACTION_STOP_BT" />
</intent-filter>
</receiver>
<receiver android:name="SMSReceiver" >

View file

@ -13,9 +13,9 @@
</style>
</head>
<body>
<h2>CrossWords 4.4.139 release</h2>
<h2>CrossWords 4.4.140 release</h2>
<p>This release fixes a bug with picking tiles face-up</p>
<p>This release updates translations for Catalan, French, German and Japanese.</p>
<div id="survey">
<p>Please <a href="https://www.surveymonkey.com/s/GX3XLHR">take
@ -25,19 +25,18 @@
<h3>New with this release</h3>
<ul>
<li>Don't put up multiple tile pickers. (This feature is rarely
used, but had apparently been broken for a while.)</li>
<li>Fix two more bugs reported by users (Thanks!)</li>
<li>Include new translations for Catalan and Norwegian</li>
<li>Include changes made by Weblate volunteers for four
languages. (Thanks!)</li>
</ul>
<p>(The full changelog
<p>(The full changelog
is <a href="http://xwords.sf.net/and_changes.php">here</a>.)</p>
<h3>Next up</h3>
<ul>
<li>Continue work to support WiFi Direct (currently working
sporadically)</li>
<li>Make changes in how background networking and bluetooth work
that Google's mandating for all releases after 1
November.</li>
</ul>
<p>Please let me know

View file

@ -33,7 +33,6 @@ import android.widget.TextView;
import java.text.DateFormat;
import java.util.Date;
import junit.framework.Assert;
import org.eehouse.android.xw4.loc.LocUtils;

View file

@ -0,0 +1,58 @@
/* -*- compile-command: "find-and-gradle.sh insXw4Deb"; -*- */
/*
* Copyright 2012 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) any later version.
*
* 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.
*/
package org.eehouse.android.xw4;
public class Assert {
private static final String TAG = Assert.class.getSimpleName();
public static void fail() {
assertTrue(false);
}
public static void assertFalse(boolean val)
{
assertTrue(! val);
}
public static void assertTrue(boolean val) {
if (! val) {
Log.e( TAG, "firing assert!" );
DbgUtils.printStack( TAG );
junit.framework.Assert.fail();
}
}
public static void assertNotNull( Object val )
{
assertTrue( val != null );
}
public static void assertNull( Object val )
{
assertTrue( val == null );
}
public static void assertEquals( Object obj1, Object obj2 )
{
assertTrue( (obj1 == null && obj2 == null)
|| (obj1 != null && obj1.equals(obj2)) );
}
}

View file

@ -1,6 +1,6 @@
/* -*- compile-command: "find-and-gradle.sh insXw4Deb"; -*- */
/*
* Copyright 2009 - 2016 by Eric House (xwords@eehouse.org). All rights
* Copyright 2009 - 2019 by Eric House (xwords@eehouse.org). All rights
* reserved.
*
* This program is free software; you can redistribute it and/or
@ -22,26 +22,87 @@ package org.eehouse.android.xw4;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.ProgressDialog;
import android.bluetooth.BluetoothDevice;
import android.content.Intent;
import android.os.Bundle;
import android.text.format.DateUtils;
import android.view.View;
import android.widget.Button;
import junit.framework.Assert;
import org.eehouse.android.xw4.DBUtils.SentInvitesInfo;
import org.eehouse.android.xw4.DlgDelegate.Action;
import java.util.Iterator;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Set;
public class BTInviteDelegate extends InviteDelegate {
private static final int[] BUTTONIDS = { R.id.button_add,
private static final String TAG = BTInviteDelegate.class.getSimpleName();
private static final String KEY_PERSIST = TAG + "_persist";
private static final int[] BUTTONIDS = { R.id.button_scan,
R.id.button_settings,
R.id.button_clear,
};
private static final boolean ENABLE_FAKER = false;
private Activity m_activity;
private TwoStringPair[] m_pairs;
private ProgressDialog m_progress;
private static class Persisted implements Serializable {
List<TwoStringPair> pairs;
// HashMap: m_stamps is serialized, so can't be abstract type
HashMap<String, Long> stamps = new HashMap<>();
void add( String devAddress, String devName ) {
// If it's already there, update it. Otherwise create new
boolean alreadyHave = false;
if ( null == pairs ) {
pairs = new ArrayList<>();
} else {
for ( TwoStringPair pair : pairs ) {
alreadyHave = pair.str2.equals(devName);
if ( alreadyHave ) {
break;
}
}
}
if ( !alreadyHave ) {
pairs.add( new TwoStringPair( devAddress, devName ) );
}
stamps.put( devName, System.currentTimeMillis() );
sort();
}
void remove(final Set<? extends InviterItem> checked)
{
for ( InviterItem item : checked ) {
TwoStringPair pair = (TwoStringPair)item;
stamps.remove( pair.str2 );
pairs.remove( pair );
}
}
boolean empty() { return pairs == null || pairs.size() == 0; }
private void sort()
{
Collections.sort( pairs, new Comparator<TwoStringPair>() {
@Override
public int compare( TwoStringPair rec1, TwoStringPair rec2 ) {
long val1 = stamps.get( rec1.str2 );
long val2 = stamps.get( rec2.str2 );
return (int)(val2 - val1);
}
});
}
}
private Persisted mPersisted;
public static void launchForResult( Activity activity, int nMissing,
SentInvitesInfo info,
@ -68,27 +129,38 @@ public class BTInviteDelegate extends InviteDelegate {
@Override
protected void init( Bundle savedInstanceState )
{
String msg = getString( R.string.bt_pick_addall_button );
msg = getQuantityString( R.plurals.invite_bt_desc_fmt, m_nMissing,
m_nMissing, msg );
String msg = getQuantityString( R.plurals.invite_bt_desc_fmt_2, m_nMissing,
m_nMissing )
+ getString( R.string.invite_bt_desc_postscript );
super.init( msg, 0 );
addButtonBar( R.layout.bt_buttons, BUTTONIDS );
BTService.clearDevices( m_activity, null ); // will return names
load();
if ( mPersisted.empty() ) {
scan();
} else {
updateListAdapter( mPersisted.pairs );
}
}
@Override
protected void onBarButtonClicked( int id )
{
switch( id ) {
case R.id.button_add:
case R.id.button_scan:
scan();
break;
case R.id.button_settings:
BTService.openBTSettings( m_activity );
break;
case R.id.button_clear:
removeSelected();
mPersisted.remove( getChecked() );
store();
clearChecked();
updateListAdapter( mPersisted.pairs );
tryEnable();
break;
}
}
@ -101,20 +173,24 @@ public class BTInviteDelegate extends InviteDelegate {
case SCAN_DONE:
post( new Runnable() {
public void run() {
synchronized( BTInviteDelegate.this ) {
m_progress.cancel();
m_pairs = null;
if ( 0 < args.length ) {
m_pairs = TwoStringPair.make( (String[])(args[0]),
(String[])(args[1]) );
}
updateListAdapter( m_pairs );
tryEnable();
if ( mPersisted.empty() ) {
makeNotAgainBuilder( R.string.not_again_emptybtscan,
R.string.key_notagain_emptybtscan )
.show();
}
}
} );
break;
case HOST_PONGED:
post( new Runnable() {
@Override
public void run() {
processScanResult( (BluetoothDevice)args[0] );
}
} );
break;
default:
super.eventOccurred( event, args );
}
@ -123,10 +199,18 @@ public class BTInviteDelegate extends InviteDelegate {
@Override
protected void onChildAdded( View child, InviterItem data )
{
TwoStrsItem item = (TwoStrsItem)child;
TwoStringPair pair = (TwoStringPair)data;
// null: we don't display mac address
((TwoStrsItem)child).setStrings( pair.str2, null );
String devName = ((TwoStringPair)data).str2;
String msg = null;
if ( mPersisted.stamps.containsKey( devName ) ) {
CharSequence elapsed = DateUtils
.getRelativeTimeSpanString( mPersisted.stamps.get( devName ),
System.currentTimeMillis(),
DateUtils.SECOND_IN_MILLIS );
msg = getString( R.string.bt_scan_age_fmt, elapsed );
}
((TwoStrsItem)child).setStrings( devName, msg );
}
@Override
@ -142,9 +226,16 @@ public class BTInviteDelegate extends InviteDelegate {
private void scan()
{
if ( ENABLE_FAKER && Utils.nextRandomInt() % 5 == 0 ) {
mPersisted.add( "00:00:00:00:00:00", "Do Not Invite Me" );
}
int count = BTService.getPairedCount( m_activity );
if ( 0 < count ) {
BTService.scan( m_activity );
String msg = getQuantityString( R.plurals.bt_scan_progress_fmt, count, count );
m_progress = ProgressDialog.show( m_activity, msg, null, true, true );
BTService.scan( m_activity, 5000 );
} else {
makeConfirmThenBuilder( R.string.bt_no_devs,
Action.OPEN_BT_PREFS_ACTION )
@ -153,17 +244,34 @@ public class BTInviteDelegate extends InviteDelegate {
}
}
// @Override
private void removeSelected()
private void processScanResult( BluetoothDevice dev )
{
Set<InviterItem> checked = getChecked();
String[] devs = new String[checked.size()];
Iterator<InviterItem> iter = checked.iterator();
for ( int ii = 0; iter.hasNext(); ++ii ) {
TwoStringPair pair = (TwoStringPair)iter.next();
devs[ii] = pair.str1;
DbgUtils.assertOnUIThread();
mPersisted.add( dev.getAddress(), dev.getName() );
store();
updateListAdapter( mPersisted.pairs );
tryEnable();
}
private void load()
{
try {
String str64 = DBUtils.getStringFor( m_activity, KEY_PERSIST, null );
mPersisted = (Persisted)Utils.string64ToSerializable( str64 );
} catch ( Exception ex ) {} // NPE, de-serialization problems, etc.
if ( null == mPersisted ) {
mPersisted = new Persisted();
}
BTService.clearDevices( m_activity, devs );
}
private void store()
{
String str64 = mPersisted == null
? "" : Utils.serializableToString64( mPersisted );
DBUtils.setStringFor( m_activity, KEY_PERSIST, str64 );
}
// DlgDelegate.DlgClickNotify interface

View file

@ -29,6 +29,9 @@ import android.content.Intent;
public class BTReceiver extends BroadcastReceiver {
private static final String TAG = BTReceiver.class.getSimpleName();
// This string is also used in AndroidManifest (as a string literal)
static final String ACTION_STOP_BT = "org.eehouse.android.ACTION_STOP_BT";
@Override
public void onReceive( Context context, Intent intent )
{
@ -37,9 +40,14 @@ public class BTReceiver extends BroadcastReceiver {
Log.d( TAG, "BTReceiver.onReceive(action=%s, intent=%s)",
action, intent.toString() );
if ( action.equals( BluetoothDevice.ACTION_ACL_CONNECTED ) ) {
BTService.startService( context );
} else if ( action.equals( BluetoothAdapter.ACTION_STATE_CHANGED ) ) {
switch (action ) {
case ACTION_STOP_BT:
BTService.stopBackground( context );
break;
case BluetoothDevice.ACTION_ACL_CONNECTED:
BTService.onACLConnected( context );
break;
case BluetoothAdapter.ACTION_STATE_CHANGED:
int newState =
intent.getIntExtra( BluetoothAdapter.EXTRA_STATE, -1 );
switch ( newState ) {

View file

@ -27,7 +27,6 @@ import java.net.Socket;
import java.util.concurrent.LinkedBlockingQueue;
import org.json.JSONObject;
import junit.framework.Assert;
public class BiDiSockWrap {
private static final String TAG = BiDiSockWrap.class.getSimpleName();

View file

@ -31,7 +31,6 @@ import android.graphics.RectF;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import junit.framework.Assert;
import org.eehouse.android.xw4.jni.BoardDims;
import org.eehouse.android.xw4.jni.CommonPrefs;

View file

@ -29,7 +29,6 @@ import android.view.View;
import android.view.View.MeasureSpec;
import android.graphics.Rect;
import junit.framework.Assert;
public class BoardContainer extends ViewGroup {
private static final String TAG = BoardContainer.class.getSimpleName();

View file

@ -49,7 +49,6 @@ import java.util.Map;
import java.util.Map;
import java.util.Set;
import junit.framework.Assert;
import org.eehouse.android.xw4.DBUtils.SentInvitesInfo;
import org.eehouse.android.xw4.DlgDelegate.Action;
@ -188,16 +187,14 @@ public class BoardDelegate extends DelegateBase
}
break;
case GAME_OVER:
case DLG_CONNSTAT: {
case GAME_OVER: {
GameSummary summary = (GameSummary)params[0];
int title = (Integer)params[1];
String msg = (String)params[2];
ab.setTitle( title )
.setMessage( msg )
.setPositiveButton( android.R.string.ok, null );
if ( DlgID.GAME_OVER == dlgID
&& rematchSupported( m_activity, true, summary ) ) {
if ( rematchSupported( m_activity, true, summary ) ) {
lstnr = new OnClickListener() {
public void onClick( DialogInterface dlg,
int whichButton ) {
@ -216,25 +213,6 @@ public class BoardDelegate extends DelegateBase
};
ab.setNeutralButton( R.string.button_archive, lstnr );
}
} else if ( DlgID.DLG_CONNSTAT == dlgID
&& BuildConfig.DEBUG && null != m_connTypes
&& (m_connTypes.contains( CommsConnType.COMMS_CONN_RELAY )
|| m_connTypes.contains( CommsConnType.COMMS_CONN_P2P )) ) {
lstnr = new OnClickListener() {
public void onClick( DialogInterface dlg,
int whichButton ) {
NetStateCache.reset( m_activity );
if ( m_connTypes.contains( CommsConnType.COMMS_CONN_RELAY ) ) {
RelayService.reset( m_activity );
}
if ( m_connTypes.contains( CommsConnType.COMMS_CONN_P2P ) ) {
WiDirService.reset( m_activity );
}
}
};
ab.setNegativeButton( R.string.button_reconnect, lstnr );
}
dialog = ab.create();
}
@ -536,7 +514,7 @@ public class BoardDelegate extends DelegateBase
finish();
}
};
alert.setNoDismissListenerNeg( ab, R.string.button_wait, lstnr );
alert.setNoDismissListenerNeg( ab, R.string.button_close, lstnr );
}
dialog = ab.create();
@ -588,17 +566,21 @@ public class BoardDelegate extends DelegateBase
m_haveInvited = args.getBoolean( GameUtils.INVITED, false );
m_overNotShown = true;
m_jniThreadRef = JNIThread.getRetained( m_rowid, true );
// getRetained() can in threory fail to get the lock and so will
// return null. Let m_jniThreadRef stay null in that case; doResume()
// will finish() in that case.
m_jniThreadRef = JNIThread.getRetained( m_activity, m_rowid, true );
if ( null != m_jniThreadRef ) {
// see http://stackoverflow.com/questions/680180/where-to-stop- \
// destroy-threads-in-android-service-class
m_jniThreadRef.setDaemonOnce( true );
m_jniThreadRef.startOnce();
// see http://stackoverflow.com/questions/680180/where-to-stop- \
// destroy-threads-in-android-service-class
m_jniThreadRef.setDaemonOnce( true ); // firing
m_jniThreadRef.startOnce();
NFCUtils.register( m_activity, this ); // Don't seem to need to unregister...
NFCUtils.register( m_activity, this ); // Don't seem to need to unregister...
setBackgroundColor();
setKeepScreenOn();
setBackgroundColor();
setKeepScreenOn();
}
} // init
@Override
@ -628,7 +610,7 @@ public class BoardDelegate extends DelegateBase
@Override
protected void onStop()
{
if ( isFinishing() ) {
if ( isFinishing() && null != m_jniThreadRef ) {
m_jniThreadRef.release();
m_jniThreadRef = null;
}
@ -1217,7 +1199,9 @@ public class BoardDelegate extends DelegateBase
RequestCode.BT_INVITE_RESULT );
break;
case SMS:
Perms23.tryGetPerms( this, Perm.SEND_SMS, R.string.sms_invite_rationale,
Perms23.tryGetPerms( this, new Perm[] { Perm.SEND_SMS,
Perm.RECEIVE_SMS },
R.string.sms_invite_rationale,
Action.INVITE_SMS, m_mySIS.nMissing, info );
break;
case RELAY:
@ -1468,6 +1452,7 @@ public class BoardDelegate extends DelegateBase
//////////////////////////////////////////////////
// ConnStatusHandler.ConnStatusCBacks
//////////////////////////////////////////////////
@Override
public void invalidateParent()
{
runOnUiThread(new Runnable() {
@ -1478,22 +1463,13 @@ public class BoardDelegate extends DelegateBase
});
}
@Override
public void onStatusClicked()
{
final String msg = ConnStatusHandler.getStatusText( m_activity, m_connTypes );
post( new Runnable() {
@Override
public void run() {
if ( null == msg ) {
askNoAddrsDelete();
} else {
showDialogFragment( DlgID.DLG_CONNSTAT, null,
R.string.info_title, msg );
}
}
} );
onStatusClicked( m_jniGamePtr );
}
@Override
public Handler getHandler()
{
return m_handler;
@ -1506,15 +1482,6 @@ public class BoardDelegate extends DelegateBase
finish();
}
private void askNoAddrsDelete()
{
makeConfirmThenBuilder( R.string.connstat_net_noaddr,
Action.DELETE_AND_EXIT )
.setPosButton( R.string.list_item_delete )
.setNegButton( R.string.button_close_game )
.show();
}
private void askDropRelay()
{
String msg = getString( R.string.confirm_drop_relay );
@ -1853,46 +1820,52 @@ public class BoardDelegate extends DelegateBase
{
int resid = 0;
boolean asToast = false;
String msg = null;
switch( code ) {
case UtilCtxt.ERR_TILES_NOT_IN_LINE:
case ERR_TILES_NOT_IN_LINE:
resid = R.string.str_tiles_not_in_line;
break;
case UtilCtxt.ERR_NO_EMPTIES_IN_TURN:
case ERR_NO_EMPTIES_IN_TURN:
resid = R.string.str_no_empties_in_turn;
break;
case UtilCtxt.ERR_TWO_TILES_FIRST_MOVE:
case ERR_TWO_TILES_FIRST_MOVE:
resid = R.string.str_two_tiles_first_move;
break;
case UtilCtxt.ERR_TILES_MUST_CONTACT:
case ERR_TILES_MUST_CONTACT:
resid = R.string.str_tiles_must_contact;
break;
case UtilCtxt.ERR_NOT_YOUR_TURN:
case ERR_NOT_YOUR_TURN:
resid = R.string.str_not_your_turn;
break;
case UtilCtxt.ERR_NO_PEEK_ROBOT_TILES:
case ERR_NO_PEEK_ROBOT_TILES:
resid = R.string.str_no_peek_robot_tiles;
break;
case UtilCtxt.ERR_NO_EMPTY_TRADE:
case ERR_NO_EMPTY_TRADE:
// This should not be possible as the button's
// disabled when no tiles selected.
Assert.fail();
break;
case UtilCtxt.ERR_TOO_FEW_TILES_LEFT_TO_TRADE:
case ERR_TOO_MANY_TRADE:
int nLeft = XwJNI.server_countTilesInPool( m_jniGamePtr );
msg = getQuantityString( R.plurals.too_many_trade_fmt,
nLeft, nLeft );
break;
case ERR_TOO_FEW_TILES_LEFT_TO_TRADE:
resid = R.string.str_too_few_tiles_left_to_trade;
break;
case UtilCtxt.ERR_CANT_UNDO_TILEASSIGN:
case ERR_CANT_UNDO_TILEASSIGN:
resid = R.string.str_cant_undo_tileassign;
break;
case UtilCtxt.ERR_CANT_HINT_WHILE_DISABLED:
case ERR_CANT_HINT_WHILE_DISABLED:
resid = R.string.str_cant_hint_while_disabled;
break;
case UtilCtxt.ERR_NO_PEEK_REMOTE_TILES:
case ERR_NO_PEEK_REMOTE_TILES:
resid = R.string.str_no_peek_remote_tiles;
break;
case UtilCtxt.ERR_REG_UNEXPECTED_USER:
case ERR_REG_UNEXPECTED_USER:
resid = R.string.str_reg_unexpected_user;
break;
case UtilCtxt.ERR_SERVER_DICT_WINS:
case ERR_SERVER_DICT_WINS:
resid = R.string.str_server_dict_wins;
break;
case ERR_REG_SERVER_SANS_REMOTE:
@ -1904,17 +1877,21 @@ public class BoardDelegate extends DelegateBase
break;
}
if ( resid != 0 ) {
if ( null == msg && resid != 0 ) {
msg = getString( resid );
}
if ( null != msg ) {
if ( asToast ) {
final int residf = resid;
final String msgf = msg;
runOnUiThread( new Runnable() {
@Override
public void run() {
showToast( residf );
showToast( msgf );
}
} );
} else {
nonBlockingDialog( DlgID.DLG_OKONLY, getString( resid ) );
nonBlockingDialog( DlgID.DLG_OKONLY, msg );
}
}
} // userError
@ -2066,9 +2043,9 @@ public class BoardDelegate extends DelegateBase
private void doResume( boolean isStart )
{
boolean success = true;
boolean success = null != m_jniThreadRef;
boolean firstStart = null == m_handler;
if ( firstStart ) {
if ( success && firstStart ) {
m_handler = new Handler();
success = m_jniThreadRef.configure( m_activity, m_view, m_utils, this,
@ -2631,9 +2608,7 @@ public class BoardDelegate extends DelegateBase
private boolean inArchiveGroup()
{
String archiveName = LocUtils
.getString( m_activity, R.string.group_name_archive );
long archiveGroup = DBUtils.getGroup( m_activity, archiveName );
long archiveGroup = DBUtils.getArchiveGroup( m_activity );
long curGroup = DBUtils.getGroupForGame( m_activity, m_rowid );
return curGroup == archiveGroup;
}
@ -2770,18 +2745,21 @@ public class BoardDelegate extends DelegateBase
GamePtr gamePtr = null;
GameSummary summary = null;
CurGameInfo gi = null;
JNIThread thread = JNIThread.getRetained( rowID );
JNIThread thread = JNIThread.getRetained( activity, rowID );
if ( null != thread ) {
gamePtr = thread.getGamePtr().retain();
summary = thread.getSummary();
gi = thread.getGI();
} else {
GameLock lock = new GameLock( rowID, false );
if ( lock.tryLock() ) {
summary = DBUtils.getSummary( activity, lock );
gi = new CurGameInfo( activity );
gamePtr = GameUtils.loadMakeGame( activity, gi, lock );
lock.unlock();
try ( GameLock lock = GameLock.getFor( rowID ).tryLockRO() ) {
if ( null != lock ) {
summary = DBUtils.getSummary( activity, lock );
gi = new CurGameInfo( activity );
gamePtr = GameUtils.loadMakeGame( activity, gi, lock );
} else {
DbgUtils.toastNoLock( TAG, activity,
"setupRematchFor(%d)", rowID );
}
}
}

View file

@ -31,7 +31,6 @@ import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import junit.framework.Assert;
import org.eehouse.android.xw4.jni.BoardDims;
import org.eehouse.android.xw4.jni.BoardHandler;
@ -46,7 +45,6 @@ public class BoardView extends View implements BoardHandler, SyncedDraw {
private static final float MIN_FONT_DIPS = 10.0f;
private static final int MULTI_INACTIVE = -1;
private static final int VERSION_CODES_N = 24; // until we're building on SDK 24...
private static boolean s_isFirstDraw;
private static int s_curGameID;
@ -205,7 +203,7 @@ public class BoardView extends View implements BoardHandler, SyncedDraw {
synchronized( this ) {
if ( layoutBoardOnce() && m_measuredFromDims ) {
Bitmap bitmap = s_bitmap;
if ( Build.VERSION.SDK_INT >= VERSION_CODES_N ) {
if ( Build.VERSION.SDK_INT >= Build.VERSION_CODES.N ) {
bitmap = Bitmap.createBitmap(bitmap);
}
canvas.drawBitmap( bitmap, 0, 0, new Paint() );
@ -224,7 +222,7 @@ public class BoardView extends View implements BoardHandler, SyncedDraw {
final int height = getHeight();
boolean layoutDone = width == m_layoutWidth && height == m_layoutHeight;
if ( layoutDone ) {
Log.d( TAG, "layoutBoardOnce(): layoutDone true" );
// Log.d( TAG, "layoutBoardOnce(): layoutDone true" );
} else if ( null == m_gi ) {
// nothing to do either
Log.d( TAG, "layoutBoardOnce(): no m_gi" );
@ -283,7 +281,7 @@ public class BoardView extends View implements BoardHandler, SyncedDraw {
m_layoutHeight = height;
layoutDone = true;
}
Log.d( TAG, "layoutBoardOnce()=>%b", layoutDone );
// Log.d( TAG, "layoutBoardOnce()=>%b", layoutDone );
return layoutDone;
} // layoutBoardOnce

View file

@ -0,0 +1,72 @@
/* -*- compile-command: "find-and-gradle.sh insXw4Deb"; -*- */
/*
* Copyright 2010 - 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) any later version.
*
* 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.
*/
package org.eehouse.android.xw4;
import android.os.Build;
import android.content.Context;
import java.util.HashSet;
import java.util.Set;
import android.app.NotificationChannel;
import android.app.NotificationManager;
public class Channels {
enum ID {
FOREGROUND(R.string.foreground_channel_expl,
NotificationManager.IMPORTANCE_LOW),
GAME_EVENT(R.string.gameevent_channel_expl,
NotificationManager.IMPORTANCE_LOW);
private int mExpl;
private int mImportance;
private ID( int expl, int imp )
{
mExpl = expl;
mImportance = imp;
}
public int getDesc() { return mExpl; }
private int getImportance() { return mImportance; }
}
private static Set<ID> sChannelsMade = new HashSet<>();
public static String getChannelID( Context context, ID id )
{
final String name = id.toString();
if ( ! sChannelsMade.contains( id ) ) {
sChannelsMade.add( id );
if ( Build.VERSION.SDK_INT >= Build.VERSION_CODES.O ) {
NotificationManager notMgr = (NotificationManager)
context.getSystemService( Context.NOTIFICATION_SERVICE );
NotificationChannel channel = notMgr.getNotificationChannel( name );
if ( channel == null ) {
String channelDescription = context.getString( id.getDesc() );
channel = new NotificationChannel( name, channelDescription,
id.getImportance() );
channel.enableVibration( true );
notMgr.createNotificationChannel( channel );
}
}
}
return name;
}
}

View file

@ -40,7 +40,6 @@ import android.widget.TextView;
import java.text.DateFormat;
import android.text.format.DateUtils;
import junit.framework.Assert;
import org.eehouse.android.xw4.DlgDelegate.Action;
import org.eehouse.android.xw4.jni.JNIThread;
@ -131,7 +130,7 @@ public class ChatDelegate extends DelegateBase {
protected void onResume()
{
super.onResume();
m_jniThreadRef = JNIThread.getRetained( m_rowid );
m_jniThreadRef = JNIThread.getRetained( m_activity, m_rowid );
if ( null == m_jniThreadRef ) {
Log.w( TAG, "onResume(): m_jniThreadRef null; exiting" );
finish();

View file

@ -22,7 +22,6 @@ package org.eehouse.android.xw4;
import android.content.Context;
import junit.framework.Assert;
import org.eehouse.android.xw4.jni.CommsAddrRec;
import org.eehouse.android.xw4.jni.CommsAddrRec.CommsConnType;

View file

@ -32,10 +32,10 @@ import android.provider.Settings;
import android.text.format.DateUtils;
import android.text.format.Time;
import junit.framework.Assert;
import org.eehouse.android.xw4.jni.CommsAddrRec.CommsConnType;
import org.eehouse.android.xw4.jni.CommsAddrRec.CommsConnTypeSet;
import org.eehouse.android.xw4.jni.CommsAddrRec;
import org.eehouse.android.xw4.jni.XwJNI;
import org.eehouse.android.xw4.loc.LocUtils;
@ -164,7 +164,9 @@ public class ConnStatusHandler {
return s_downOnMe && s_rect.contains( xx, yy );
}
public static String getStatusText( Context context, CommsConnTypeSet connTypes )
public static String getStatusText( Context context,
CommsConnTypeSet connTypes,
CommsAddrRec addr )
{
String msg;
if ( null == connTypes || 0 == connTypes.size() ) {
@ -177,9 +179,12 @@ public class ConnStatusHandler {
R.string.connstat_net_fmt,
connTypes.toString( context, true )));
for ( CommsConnType typ : connTypes.getTypes() ) {
String did = addDebugInfo( context, typ );
sb.append( String.format( "\n\n*** %s %s***\n",
typ.longName( context ), did ) );
sb.append( String.format( "\n\n*** %s ", typ.longName( context ) ) );
String did = addDebugInfo( context, addr, typ );
if ( null != did ) {
sb.append( did ).append( " " );
}
sb.append( "***\n" );
// For sends we list failures too.
SuccessRecord record = recordFor( context, typ, false );
@ -533,23 +538,39 @@ public class ConnStatusHandler {
return result;
}
private static String addDebugInfo( Context context, CommsConnType typ )
private static String addDebugInfo( Context context, CommsAddrRec addr,
CommsConnType typ )
{
String result = "";
String result = null;
if ( BuildConfig.DEBUG ) {
switch ( typ ) {
case COMMS_CONN_RELAY:
result = String.format( "(DevID: %d; host: %s) ",
result = String.format( "DevID: %d; host: %s",
DevID.getRelayDevIDInt(context),
XWPrefs.getDefaultRelayHost(context) );
break;
case COMMS_CONN_P2P:
result = WiDirService.formatNetStateInfo();
break;
case COMMS_CONN_BT:
if ( null != addr ) {
result = addr.bt_hostName;
}
break;
case COMMS_CONN_SMS:
if ( null != addr ) {
result = addr.sms_phone;
}
break;
default:
break;
}
}
if ( null != result ) {
result = "(" + result + ")";
}
return result;
}
}

View file

@ -30,7 +30,6 @@ import android.widget.LinearLayout;
import java.util.Map;
import junit.framework.Assert;
import org.eehouse.android.xw4.jni.CommsAddrRec.CommsConnType;
import org.eehouse.android.xw4.jni.CommsAddrRec.CommsConnTypeSet;

View file

@ -34,7 +34,6 @@ import org.eehouse.android.xw4.loc.LocUtils;
import java.io.Serializable;
import junit.framework.Assert;
public class DBAlert extends XWDialogFragment {
private static final String TAG = DBAlert.class.getSimpleName();

View file

@ -33,7 +33,6 @@ import android.graphics.BitmapFactory;
import android.os.Environment;
import android.text.TextUtils;
import junit.framework.Assert;
import org.eehouse.android.xw4.DictUtils.DictLoc;
import org.eehouse.android.xw4.DlgDelegate.DlgClickNotify.InviteMeans;
@ -367,22 +366,27 @@ public class DBUtils {
public static void addRematchInfo( Context context, long rowid, String btAddr,
String phone, String relayID, String p2pAddr )
{
GameLock lock = new GameLock( rowid, true ).lock();
GameSummary summary = getSummary( context, lock );
if ( null != btAddr ) {
summary.putStringExtra( GameSummary.EXTRA_REMATCH_BTADDR, btAddr );
try ( GameLock lock = GameLock.getFor( rowid ).tryLock() ) {
Assert.assertNotNull( lock );
if ( null != lock ) {
GameSummary summary = getSummary( context, lock );
if ( null != btAddr ) {
summary.putStringExtra( GameSummary.EXTRA_REMATCH_BTADDR, btAddr );
}
if ( null != phone ) {
summary.putStringExtra( GameSummary.EXTRA_REMATCH_PHONE, phone );
}
if ( null != relayID ) {
summary.putStringExtra( GameSummary.EXTRA_REMATCH_RELAY, relayID );
}
if ( null != p2pAddr ) {
summary.putStringExtra( GameSummary.EXTRA_REMATCH_P2P, p2pAddr );
}
saveSummary( context, lock, summary );
} else {
Log.e( TAG, "addRematchInfo(%d): unable to lock game" );
}
}
if ( null != phone ) {
summary.putStringExtra( GameSummary.EXTRA_REMATCH_PHONE, phone );
}
if ( null != relayID ) {
summary.putStringExtra( GameSummary.EXTRA_REMATCH_RELAY, relayID );
}
if ( null != p2pAddr ) {
summary.putStringExtra( GameSummary.EXTRA_REMATCH_P2P, p2pAddr );
}
saveSummary( context, lock, summary );
lock.unlock();
}
public static int countGamesUsingLang( Context context, int lang )
@ -1053,7 +1057,8 @@ public class DBUtils {
setCached( rowid, null ); // force reread
lock = new GameLock( rowid, true ).lock();
lock = GameLock.getFor( rowid ).tryLock();
Assert.assertNotNull( lock );
notifyListeners( rowid, GameChangeType.GAME_CREATED );
}
@ -1114,14 +1119,14 @@ public class DBUtils {
public static void deleteGame( Context context, long rowid )
{
GameLock lock = new GameLock( rowid, true ).lock( 300 );
if ( null != lock ) {
deleteGame( context, lock );
lock.unlock();
} else {
Log.e( TAG, "deleteGame: unable to lock rowid %d", rowid );
if ( BuildConfig.DEBUG ) {
Assert.fail();
try ( GameLock lock = GameLock.getFor( rowid ).lock( 300 ) ) {
if ( null != lock ) {
deleteGame( context, lock );
} else {
Log.e( TAG, "deleteGame: unable to lock rowid %d", rowid );
if ( BuildConfig.DEBUG ) {
Assert.fail();
}
}
}
}
@ -1786,6 +1791,14 @@ public class DBUtils {
invalGroupsCache();
}
public static long getArchiveGroup( Context context )
{
String archiveName = LocUtils
.getString( context, R.string.group_name_archive );
long archiveGroup = getGroup( context, archiveName );
return archiveGroup;
}
// Change group id of a game
public static void moveGame( Context context, long rowid, long groupID )
{
@ -2395,7 +2408,7 @@ public class DBUtils {
public static void setIntFor( Context context, String key, int value )
{
// Log.df( "DBUtils.setIntFor(key=%s, val=%d)", key, value );
// Log.d( TAG, "DBUtils.setIntFor(key=%s, val=%d)", key, value );
String asStr = String.format( "%d", value );
setStringFor( context, key, asStr );
}
@ -2406,7 +2419,7 @@ public class DBUtils {
if ( null != asStr ) {
dflt = Integer.parseInt( asStr );
}
// Log.df( "DBUtils.getIntFor(key=%s)=>%d", key, dflt );
// Log.d( TAG, "DBUtils.getIntFor(key=%s)=>%d", key, dflt );
return dflt;
}

View file

@ -25,7 +25,6 @@ import android.content.Intent;
import android.database.Cursor;
import android.database.DatabaseUtils;
import android.os.Bundle;
import android.os.Looper;
import android.text.TextUtils;
import android.text.format.Time;
@ -34,7 +33,6 @@ import java.util.Formatter;
import java.util.Iterator;
import java.util.Set;
import junit.framework.Assert;
import org.eehouse.android.xw4.loc.LocUtils;
@ -101,9 +99,24 @@ public class DbgUtils {
showf( context, LocUtils.getString( context, formatid ), args );
} // showf
public static void toastNoLock( String tag, Context context, String format,
Object... args )
{
format = "Unable to lock game; " + format;
if ( BuildConfig.DEBUG ) {
showf( context, format, args );
}
Log.w( tag, format, args );
}
public static void assertOnUIThread()
{
Assert.assertTrue( Looper.getMainLooper().equals(Looper.myLooper()) );
assertOnUIThread( true );
}
public static void assertOnUIThread( boolean isOnThread )
{
Assert.assertTrue( isOnThread == Utils.isOnUIThread() );
}
public static void printStack( String tag, StackTraceElement[] trace )
@ -123,6 +136,12 @@ public class DbgUtils {
}
}
public static void printStack( String tag, Exception ex )
{
String stackTrace = android.util.Log.getStackTraceString(ex);
Log.d( tag, stackTrace );
}
static String extrasToString( Intent intent )
{
Bundle bundle = intent.getExtras();

View file

@ -22,12 +22,15 @@ package org.eehouse.android.xw4;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.support.v4.app.DialogFragment;
import android.content.Context;
import android.content.DialogInterface.OnCancelListener;
import android.content.DialogInterface.OnClickListener;
import android.content.DialogInterface;
import android.content.Intent;
import android.graphics.Point;
import android.graphics.Rect;
import android.os.Bundle;
import android.support.v4.app.DialogFragment;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.ContextMenu;
import android.view.LayoutInflater;
@ -40,8 +43,6 @@ import android.widget.CheckBox;
import android.widget.EditText;
import android.widget.TextView;
import junit.framework.Assert;
import org.eehouse.android.xw4.DlgDelegate.Action;
import org.eehouse.android.xw4.DlgDelegate.ActionPair;
import org.eehouse.android.xw4.DlgDelegate.ConfirmThenBuilder;
@ -49,6 +50,12 @@ import org.eehouse.android.xw4.DlgDelegate.DlgClickNotify;
import org.eehouse.android.xw4.DlgDelegate.NotAgainBuilder;
import org.eehouse.android.xw4.DlgDelegate.OkOnlyBuilder;
import org.eehouse.android.xw4.MultiService.MultiEvent;
import org.eehouse.android.xw4.jni.CommsAddrRec.CommsConnType;
import org.eehouse.android.xw4.jni.CommsAddrRec.CommsConnTypeSet;
import org.eehouse.android.xw4.jni.CommsAddrRec;
import org.eehouse.android.xw4.jni.GameSummary;
import org.eehouse.android.xw4.jni.XwJNI.GamePtr;
import org.eehouse.android.xw4.jni.XwJNI;
import org.eehouse.android.xw4.loc.LocUtils;
import java.lang.ref.WeakReference;
@ -144,14 +151,15 @@ public class DelegateBase implements DlgClickNotify,
protected void onResume()
{
m_isVisible = true;
XWService.setListener( this );
XWServiceHelper.setListener( this );
runIfVisible();
}
protected void onPause()
{
m_isVisible = false;
XWService.setListener( null );
XWServiceHelper.setListener( null );
m_dlgDelegate.onPausing();
}
protected DelegateBase curThis()
@ -415,10 +423,44 @@ public class DelegateBase implements DlgClickNotify,
protected Dialog makeDialog( DBAlert alert, Object[] params )
{
Dialog dialog = null;
DlgID dlgID = alert.getDlgID();
Log.d( TAG, "%s.makeDialog(): not handling %s", getClass().getSimpleName(),
dlgID.toString() );
return null;
switch ( dlgID ) {
case DLG_CONNSTAT: {
AlertDialog.Builder ab = makeAlertBuilder();
GameSummary summary = (GameSummary)params[0];
int title = (Integer)params[1];
String msg = (String)params[2];
final CommsConnTypeSet conTypes = summary.conTypes;
ab.setTitle( title )
.setMessage( msg )
.setPositiveButton( android.R.string.ok, null );
if ( BuildConfig.DEBUG && null != conTypes
&& (conTypes.contains( CommsConnType.COMMS_CONN_RELAY )
|| conTypes.contains( CommsConnType.COMMS_CONN_P2P )) ) {
OnClickListener lstnr = new OnClickListener() {
public void onClick( DialogInterface dlg,
int whichButton ) {
NetStateCache.reset( m_activity );
if ( conTypes.contains( CommsConnType.COMMS_CONN_RELAY ) ) {
RelayService.reset( getActivity() );
}
if ( conTypes.contains( CommsConnType.COMMS_CONN_P2P ) ) {
WiDirService.reset( getActivity() );
}
}
};
ab.setNegativeButton( R.string.button_reconnect, lstnr );
}
dialog = ab.create();
}
break;
default:
Log.d( TAG, "%s.makeDialog(): not handling %s", getClass().getSimpleName(),
dlgID.toString() );
break;
}
return dialog;
}
protected void showDialogFragment( final DlgID dlgID, final Object... params )
@ -594,6 +636,53 @@ public class DelegateBase implements DlgClickNotify,
runIfVisible();
}
public void onStatusClicked( GamePtr gamePtr )
{
Context context = getActivity();
CommsAddrRec[] addrs = XwJNI.comms_getAddrs( gamePtr );
CommsAddrRec addr = null != addrs && 0 < addrs.length ? addrs[0] : null;
final GameSummary summary = GameUtils.getSummary( context, gamePtr.getRowid(), 1 );
if ( null != summary ) {
final String msg = ConnStatusHandler
.getStatusText( context, summary.conTypes, addr );
post( new Runnable() {
@Override
public void run() {
if ( null == msg ) {
askNoAddrsDelete();
} else {
showDialogFragment( DlgID.DLG_CONNSTAT, summary,
R.string.info_title, msg );
}
}
} );
}
}
public void onStatusClicked( long rowid )
{
Log.d( TAG, "onStatusClicked(%d)", rowid );
try ( GameLock lock = GameLock.getFor( rowid ).tryLockRO() ) {
if ( null != lock ) {
GamePtr gamePtr = GameUtils.loadMakeGame( getActivity(), lock );
if ( null != gamePtr ) {
onStatusClicked( gamePtr );
gamePtr.release();
}
}
}
}
protected void askNoAddrsDelete()
{
makeConfirmThenBuilder( R.string.connstat_net_noaddr,
Action.DELETE_AND_EXIT )
.setPosButton( R.string.list_item_delete )
.setNegButton( R.string.button_close_game )
.show();
}
//////////////////////////////////////////////////
// MultiService.MultiEventListener interface
//////////////////////////////////////////////////
@ -601,10 +690,6 @@ public class DelegateBase implements DlgClickNotify,
{
int fmtId = 0;
switch( event ) {
case BT_ERR_COUNT:
int count = (Integer)args[0];
Log.i( TAG, "Bluetooth error count: %d", count );
break;
case BAD_PROTO_BT:
fmtId = R.string.bt_bad_proto_fmt;
break;

View file

@ -37,7 +37,6 @@ import android.widget.SectionIndexer;
import android.widget.Spinner;
import android.widget.TextView;
import junit.framework.Assert;
import org.eehouse.android.xw4.DlgDelegate.Action;
import org.eehouse.android.xw4.jni.JNIUtilsImpl;

View file

@ -25,7 +25,6 @@ import android.content.res.Resources;
import android.os.Handler;
import android.widget.ArrayAdapter;
import junit.framework.Assert;
import org.eehouse.android.xw4.DictUtils.DictAndLoc;
import org.eehouse.android.xw4.DictUtils.DictLoc;
@ -327,7 +326,7 @@ public class DictLangCache {
public static void inval( final Context context, String name,
DictLoc loc, boolean added )
{
DBUtils.dictsRemoveInfo( context, name );
DBUtils.dictsRemoveInfo( context, DictUtils.removeDictExtn( name ) );
if ( added ) {
DictAndLoc dal = new DictAndLoc( name, loc );

View file

@ -24,7 +24,6 @@ import android.content.Context;
import android.content.res.AssetManager;
import android.os.Environment;
import junit.framework.Assert;
import org.eehouse.android.xw4.jni.JNIUtilsImpl;
import org.eehouse.android.xw4.jni.XwJNI;

View file

@ -45,7 +45,6 @@ import android.widget.ListView;
import android.widget.PopupMenu;
import android.widget.TextView;
import junit.framework.Assert;
import org.eehouse.android.xw4.DictUtils.DictAndLoc;
import org.eehouse.android.xw4.DictUtils.DictLoc;
@ -1064,23 +1063,6 @@ public class DictsDelegate extends ListDelegateBase
return items;
}
// private static Intent mkDownloadIntent( Context context, String dict_url )
// {
// Uri uri = Uri.parse( dict_url );
// Intent intent = new Intent( Intent.ACTION_VIEW, uri );
// intent.setFlags( Intent.FLAG_ACTIVITY_NEW_TASK );
// return intent;
// }
private static Intent mkDownloadIntent( Context context,
int lang, String dict )
{
Assert.fail();
return null;
// String dict_url = Utils.makeDictUri( context, lang, dict );
// return mkDownloadIntent( context, dict_url );
}
public static void downloadForResult( Delegator delegator, RequestCode requestCode,
int lang )
{

View file

@ -31,7 +31,6 @@ import android.widget.TextView;
import org.eehouse.android.xw4.jni.CommsAddrRec.CommsConnType;
import junit.framework.Assert;
public class DisablesItem extends LinearLayout {
private CommsConnType m_type;

View file

@ -34,7 +34,6 @@ import android.view.View;
import java.io.Serializable;
import junit.framework.Assert;
import org.eehouse.android.xw4.DBUtils.SentInvitesInfo;
@ -327,6 +326,11 @@ public class DlgDelegate {
m_handler = new Handler();
}
void onPausing()
{
stopProgress();
}
private void showOKOnlyDialogThen( String msg, Action action,
Object[] params, int titleId )
{

View file

@ -31,7 +31,6 @@ import org.eehouse.android.xw4.DlgDelegate.Action;
import org.eehouse.android.xw4.DlgDelegate.ActionPair;
import org.eehouse.android.xw4.DlgDelegate.DlgClickNotify;
import junit.framework.Assert;
import org.eehouse.android.xw4.loc.LocUtils;

View file

@ -32,7 +32,6 @@ import java.util.List;
import org.eehouse.android.xw4.DlgDelegate.Action;
import org.eehouse.android.xw4.DlgDelegate.ActionPair;
import junit.framework.Assert;
public class DlgState implements Parcelable {
private static final String TAG = DlgState.class.getSimpleName();

View file

@ -34,7 +34,6 @@ import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.TextView;
import junit.framework.Assert;
import java.io.File;
import java.io.FileOutputStream;

View file

@ -30,7 +30,6 @@ import android.widget.AdapterView;
import android.widget.Button;
import android.widget.Spinner;
import junit.framework.Assert;
import org.eehouse.android.xw4.loc.LocUtils;

View file

@ -29,7 +29,6 @@ import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.view.View;
import junit.framework.Assert;
import java.lang.ref.WeakReference;
import java.util.ArrayList;

View file

@ -45,7 +45,6 @@ import android.widget.TextView;
import java.util.HashMap;
import java.util.Map;
import junit.framework.Assert;
import org.eehouse.android.xw4.DictLangCache.LangsArrayAdapter;
import org.eehouse.android.xw4.DlgDelegate.Action;
@ -484,7 +483,7 @@ public class GameConfigDelegate extends DelegateBase
@Override
protected void onResume()
{
m_jniThread = JNIThread.getRetained( m_rowid );
m_jniThread = JNIThread.getRetained( m_activity, m_rowid );
super.onResume();
loadGame();
}
@ -537,11 +536,14 @@ public class GameConfigDelegate extends DelegateBase
if ( null == m_giOrig ) {
m_giOrig = new CurGameInfo( m_activity );
GameLock gameLock;
XwJNI.GamePtr gamePtr;
GameLock gameLock = null;
XwJNI.GamePtr gamePtr = null;
if ( null == m_jniThread ) {
gameLock = new GameLock( m_rowid, false ).lock();
gamePtr = GameUtils.loadMakeGame( m_activity, m_giOrig, gameLock );
gameLock = GameLock.getFor( m_rowid ).tryLockRO();
if ( null != gameLock ) {
gamePtr = GameUtils.loadMakeGame( m_activity, m_giOrig,
gameLock );
}
} else {
gameLock = m_jniThread.getLock();
gamePtr = m_jniThread.getGamePtr();
@ -1232,7 +1234,7 @@ public class GameConfigDelegate extends DelegateBase
{
if ( !isFinishing() ) {
GameLock gameLock = m_jniThread == null
? new GameLock( m_rowid, true ).lock()
? GameLock.getFor( m_rowid ).tryLock()
: m_jniThread.getLock();
GameUtils.applyChanges( m_activity, m_gi, m_car, m_disabMap,
gameLock, forceNew );

View file

@ -33,7 +33,6 @@ import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import junit.framework.Assert;
import org.eehouse.android.xw4.jni.GameSummary;
import org.eehouse.android.xw4.jni.JNIThread;

View file

@ -20,179 +20,223 @@
package org.eehouse.android.xw4;
import junit.framework.Assert;
import java.io.Serializable;
import java.lang.ref.WeakReference;
import java.util.HashMap;
import java.util.Map;
import java.util.Stack;
import android.support.annotation.NonNull;
// Implements read-locks and write-locks per game. A read lock is
// obtainable when other read locks are granted but not when a
// write lock is. Write-locks are exclusive.
public class GameLock {
public class GameLock implements AutoCloseable {
private static final String TAG = GameLock.class.getSimpleName();
private static final boolean DEBUG_LOCKS = false;
private static final boolean THROW_ON_LOCKED = true;
private static final int SLEEP_TIME = 100;
private static final long ASSERT_TIME = 2000;
// private static final long ASSERT_TIME = 2000;
private static final long THROW_TIME = 1000;
private long m_rowid;
private boolean m_isForWrite;
private int m_lockCount;
private Thread m_ownerThread;
private StackTraceElement[] m_lockTrace;
private Stack<Owner> mOwners = new Stack<>();
private boolean mReadOnly;
static {
Assert.assertTrue( THROW_TIME <= ASSERT_TIME );
private static class Owner {
Thread mThread;
String mTrace;
Owner()
{
mThread = Thread.currentThread();
// mTrace = mThread.getStackTrace();
mTrace = android.util.Log.getStackTraceString(new Exception());
}
@Override
public String toString()
{
return String.format( "Owner: {%s/%s}", mThread, mTrace );
}
}
@Override
public String toString()
{
return String.format("{this: %H; rowid: %d; count: %d; ro: %b}",
this, m_rowid, mOwners.size(), mReadOnly);
}
public static class GameLockedException extends RuntimeException {}
private static HashMap<Long, GameLock>
s_locks = new HashMap<Long,GameLock>();
public GameLock( long rowid, boolean isForWrite )
{
Assert.assertTrue( DBUtils.ROWID_NOTFOUND != rowid );
m_rowid = rowid;
m_isForWrite = isForWrite;
m_lockCount = 0;
if ( DEBUG_LOCKS ) {
Log.i( TAG, "GameLock(rowid:%d,isForWrite:%b)=>this: %H",
rowid, isForWrite, this );
DbgUtils.printStack( TAG );
}
}
// This could be written to allow multiple read locks. Let's
// see if not doing that causes problems.
public boolean tryLock()
{
return null == tryLockImpl(); // unowned?
}
private GameLock tryLockImpl()
{
GameLock owner = null;
boolean gotIt = false;
synchronized( s_locks ) {
owner = s_locks.get( m_rowid );
if ( null == owner ) { // unowned
Assert.assertTrue( 0 == m_lockCount );
s_locks.put( m_rowid, this );
if ( BuildConfig.DEBUG ) {
m_ownerThread = Thread.currentThread();
}
++m_lockCount;
gotIt = true;
if ( DEBUG_LOCKS ) {
StackTraceElement[] trace
= Thread.currentThread().getStackTrace();
m_lockTrace = new StackTraceElement[trace.length];
System.arraycopy( trace, 0, m_lockTrace, 0, trace.length );
}
} else if ( this == owner && ! m_isForWrite ) {
if ( DEBUG_LOCKS ) {
Log.i( TAG, "tryLock(): incrementing lock count" );
}
Assert.assertTrue( 0 == m_lockCount );
++m_lockCount;
gotIt = true;
owner = null;
} else if ( DEBUG_LOCKS ) {
Log.i( TAG, "tryLock(): rowid %d already held by lock %H",
m_rowid, owner );
}
}
if ( DEBUG_LOCKS ) {
Log.i( TAG, "tryLock %H (rowid=%d) => %b", this, m_rowid, gotIt );
}
return owner;
}
// Wait forever (but may assert if too long)
public GameLock lock()
{
return this.lock( 0 );
}
// Version that's allowed to return null -- if maxMillis > 0
public GameLock lock( long maxMillis )
private static Map<Long, WeakReference<GameLock>> sLockMap = new HashMap<>();
public static GameLock getFor( long rowid )
{
GameLock result = null;
Assert.assertTrue( maxMillis <= ASSERT_TIME );
Assert.assertTrue( maxMillis <= THROW_TIME );
long sleptTime = 0;
synchronized ( sLockMap ) {
if ( sLockMap.containsKey( rowid ) ) {
result = sLockMap.get( rowid ).get();
}
if ( null == result ) {
result = new GameLock( rowid );
sLockMap.put( rowid, new WeakReference(result) );
}
}
return result;
}
private GameLock( long rowid ) { m_rowid = rowid; }
// We grant a lock IFF:
// * Count is 0
// OR
// * existing locks are ReadOnly and this request is readOnly
// OR
// * the requesting thread already holds the lock
private GameLock tryLockImpl( boolean readOnly )
{
GameLock result = null;
synchronized ( mOwners ) {
if ( DEBUG_LOCKS ) {
Log.d( TAG, "%s.tryLockImpl(ro=%b)", this, readOnly );
}
// Thread thisThread = Thread.currentThread();
boolean grant = false;
if ( mOwners.empty() ) {
grant = true;
} else if ( mReadOnly && readOnly ) {
grant = true;
// } else if ( thisThread == mOwnerThread ) {
// grant = true;
}
if ( grant ) {
mOwners.push( new Owner() );
mReadOnly = readOnly;
result = this;
}
if ( DEBUG_LOCKS ) {
Log.d( TAG, "%s.tryLockImpl(ro=%b) => %s", this, readOnly, result );
if ( null == result ) {
Log.d( TAG, "Unable to lock; cur owner: %s; would-be owner: %s",
mOwners.peek(), new Owner() );
}
}
}
return result;
}
// // This could be written to allow multiple read locks. Let's
// // see if not doing that causes problems.
public GameLock tryLock()
{
return tryLockImpl( false );
}
public GameLock tryLockRO()
{
return tryLockImpl( true );
}
private GameLock lockImpl( long timeoutMS, boolean readOnly ) throws InterruptedException
{
long startMS = System.currentTimeMillis();
long endMS = startMS + timeoutMS;
synchronized ( mOwners ) {
while ( null == tryLockImpl( readOnly ) ) {
long now = System.currentTimeMillis();
if ( now >= endMS ) {
throw new GameLockedException();
}
mOwners.wait( endMS - now );
}
}
if ( DEBUG_LOCKS ) {
Log.i( TAG, "lock %H (rowid:%d, maxMillis=%d)", this, m_rowid,
maxMillis );
long tookMS = System.currentTimeMillis() - startMS;
Log.d( TAG, "%s.lockImpl() returning after %d ms", this, tookMS );
}
return this;
}
for ( ; ; ) {
GameLock curOwner = tryLockImpl();
if ( null == curOwner ) {
result = this;
break;
}
if ( DEBUG_LOCKS ) {
Log.i( TAG, "lock() %H failed; sleeping", this );
if ( 0 == sleptTime || sleptTime + SLEEP_TIME >= ASSERT_TIME ) {
Log.i( TAG, "lock %H seeking stack:", curOwner );
DbgUtils.printStack( TAG, curOwner.m_lockTrace );
Log.i( TAG, "lock %H seeking stack:", this );
DbgUtils.printStack( TAG );
}
}
try {
Thread.sleep( SLEEP_TIME ); // milliseconds
sleptTime += SLEEP_TIME;
} catch( InterruptedException ie ) {
Log.ex( TAG, ie );
break;
}
if ( DEBUG_LOCKS ) {
Log.i( TAG, "lock() %H awake; "
+ "sleptTime now %d millis", this, sleptTime );
}
if ( 0 < maxMillis && sleptTime >= maxMillis ) {
break;
} else if ( THROW_ON_LOCKED && sleptTime >= THROW_TIME ) {
throw new GameLockedException();
} else if ( sleptTime >= ASSERT_TIME ) {
if ( DEBUG_LOCKS ) {
Log.i( TAG, "lock %H overlocked", this );
}
Assert.fail();
}
@NonNull
public GameLock lock() throws InterruptedException
{
if ( BuildConfig.DEBUG ) {
DbgUtils.assertOnUIThread( false );
}
// DbgUtils.logf( "GameLock.lock(%s) done", m_path );
GameLock result = lockImpl( Long.MAX_VALUE, false );
Assert.assertNotNull( result );
return result;
}
// @NonNull
// public GameLock lockRO() throws InterruptedException
// {
// if ( BuildConfig.DEBUG ) {
// DbgUtils.assertOnUIThread( false );
// }
// GameLock result = lockImpl( Long.MAX_VALUE, true );
// Assert.assertNotNull( result );
// return result;
// }
// Version that's allowed to return null -- if maxMillis > 0
public GameLock lock( long maxMillis ) throws GameLockedException
{
Assert.assertTrue( maxMillis <= THROW_TIME );
GameLock result = null;
try {
result = lockImpl( maxMillis, false );
} catch (InterruptedException ex) {
Log.d( TAG, "lock(): got %s", ex.getMessage() );
}
if ( DEBUG_LOCKS ) {
Log.d( TAG, "%s.lock(%d) => %s", this, maxMillis, result );
}
return result;
}
public GameLock lockRO( long maxMillis )
{
Assert.assertTrue( maxMillis <= THROW_TIME );
GameLock lock = null;
try {
lock = lockImpl( maxMillis, true );
} catch ( InterruptedException ex ) {
}
return lock;
}
public void unlock()
{
// DbgUtils.logf( "GameLock.unlock(%s)", m_path );
synchronized( s_locks ) {
Assert.assertTrue( this == s_locks.get(m_rowid) );
// Need to get this working before can switch to using ReentrantLock
// if ( BuildConfig.DEBUG ) {
// Assert.assertTrue( Thread.currentThread().equals(m_ownerThread) );
// }
if ( 1 == m_lockCount ) {
s_locks.remove( m_rowid );
} else {
Assert.assertTrue( !m_isForWrite );
}
--m_lockCount;
synchronized ( mOwners ) {
if ( DEBUG_LOCKS ) {
Log.i( TAG, "unlock: this: %H (rowid:%d) unlocked", this, m_rowid );
Log.d( TAG, "%s.unlock()", this );
}
Thread oldThread = mOwners.pop().mThread;
// It's ok for different threads to hold the same RO lock
if ( !mReadOnly && oldThread != Thread.currentThread() ) {
Log.e( TAG, "unlock(): unequal threads: %s => %s", oldThread,
Thread.currentThread() );
Assert.fail();
}
if ( mOwners.empty() ) {
mOwners.notifyAll();
}
if ( DEBUG_LOCKS ) {
Log.d( TAG, "%s.unlock() DONE", this );
}
}
}
@Override
public void close()
{
unlock();
}
public long getRowid()
{
return m_rowid;
@ -201,9 +245,9 @@ public class GameLock {
// used only for asserts
public boolean canWrite()
{
boolean result = m_isForWrite && 1 == m_lockCount;
boolean result = !mReadOnly; // && 1 == mLockCount[0];
if ( !result ) {
Log.w( TAG, "canWrite(): %H, returning false", this );
Log.w( TAG, "%s.canWrite(): => false", this );
}
return result;
}

View file

@ -31,7 +31,6 @@ import android.text.Html;
import android.text.TextUtils;
import android.view.Display;
import junit.framework.Assert;
import org.eehouse.android.xw4.jni.CommonPrefs;
import org.eehouse.android.xw4.jni.CommsAddrRec;
@ -93,9 +92,12 @@ public class GameUtils {
public static byte[] savedGame( Context context, long rowid )
{
GameLock lock = new GameLock( rowid, false ).lock();
byte[] result = savedGame( context, lock );
lock.unlock();
byte[] result = null;
try (GameLock lock = GameLock.getFor( rowid ).tryLockRO() ) {
if ( null != lock ) {
result = savedGame( context, lock );
}
}
if ( null == result ) {
throw new NoSuchGameException();
@ -145,7 +147,7 @@ public class GameUtils {
if ( null == lockDest ) {
long groupID = DBUtils.getGroupForGame( context, lockSrc.getRowid() );
long rowid = saveNewGame( context, gamePtr, gi, groupID );
lockDest = new GameLock( rowid, true ).lock();
lockDest = GameLock.getFor( rowid ).tryLock();
} else {
saveGame( context, gamePtr, gi, lockDest, true );
}
@ -158,16 +160,17 @@ public class GameUtils {
public static boolean resetGame( Context context, long rowidIn )
{
boolean success = false;
GameLock lock = new GameLock( rowidIn, true ).lock( 500 );
if ( null != lock ) {
tellDied( context, lock, true );
resetGame( context, lock, lock, false );
lock.unlock();
try ( GameLock lock = GameLock.getFor( rowidIn ).lock( 500 ) ) {
if ( null != lock ) {
tellDied( context, lock, true );
resetGame( context, lock, lock, false );
Utils.cancelNotification( context, (int)rowidIn );
success = true;
} else {
Log.w( TAG, "resetGame: unable to open rowid %d", rowidIn );
Utils.cancelNotification( context, (int)rowidIn );
success = true;
} else {
DbgUtils.toastNoLock( TAG, context, "resetGame(): rowid %d",
rowidIn );
}
}
return success;
}
@ -216,13 +219,13 @@ public class GameUtils {
long maxMillis )
{
GameSummary result = null;
JNIThread thread = JNIThread.getRetained( rowid );
JNIThread thread = JNIThread.getRetained( context, rowid );
GameLock lock = null;
if ( null != thread ) {
lock = thread.getLock();
} else {
try {
lock = new GameLock( rowid, false ).lock( maxMillis );
lock = GameLock.getFor( rowid ).lockRO( maxMillis );
} catch ( GameLock.GameLockedException gle ) {
Log.ex( TAG, gle );
}
@ -249,11 +252,11 @@ public class GameUtils {
long rowid = DBUtils.ROWID_NOTFOUND;
GameLock lockSrc = null;
JNIThread thread = JNIThread.getRetained( rowidIn, false );
JNIThread thread = JNIThread.getRetained( context, rowidIn );
if ( null != thread ) {
lockSrc = thread.getLock();
} else {
lockSrc = new GameLock( rowidIn, false ).lock( 300 );
lockSrc = GameLock.getFor( rowidIn ).lockRO( 300 );
}
if ( null != lockSrc ) {
@ -275,9 +278,13 @@ public class GameUtils {
public static void deleteGame( Context context, GameLock lock, boolean informNow )
{
tellDied( context, lock, informNow );
Utils.cancelNotification( context, (int)lock.getRowid() );
DBUtils.deleteGame( context, lock );
if ( null != lock ) {
tellDied( context, lock, informNow );
Utils.cancelNotification( context, (int)lock.getRowid() );
DBUtils.deleteGame( context, lock );
} else {
Log.e( TAG, "deleteGame(): null lock; doing nothing" );
}
}
public static boolean deleteGame( Context context, long rowid,
@ -285,14 +292,15 @@ public class GameUtils {
{
boolean success;
// does this need to be synchronized?
GameLock lock = new GameLock( rowid, true );
if ( lock.tryLock() ) {
deleteGame( context, lock, informNow );
lock.unlock();
success = true;
} else {
Log.w( TAG, "deleteGame: unable to delete rowid %d", rowid );
success = false;
try ( GameLock lock = GameLock.getFor( rowid ).tryLock() ) {
if ( null != lock ) {
deleteGame( context, lock, informNow );
success = true;
} else {
DbgUtils.toastNoLock( TAG, context, "deleteGame(): rowid %d",
rowid );
success = false;
}
}
return success;
}
@ -389,16 +397,16 @@ public class GameUtils {
public static Bitmap loadMakeBitmap( Context context, long rowid )
{
Bitmap thumb = null;
GameLock lock = new GameLock( rowid, false );
if ( lock.tryLock() ) {
CurGameInfo gi = new CurGameInfo( context );
GamePtr gamePtr = loadMakeGame( context, gi, lock );
if ( null != gamePtr ) {
thumb = takeSnapshot( context, gamePtr, gi );
gamePtr.release();
DBUtils.saveThumbnail( context, lock, thumb );
try ( GameLock lock = GameLock.getFor( rowid ).tryLockRO() ) {
if ( null != lock ) {
CurGameInfo gi = new CurGameInfo( context );
GamePtr gamePtr = loadMakeGame( context, gi, lock );
if ( null != gamePtr ) {
thumb = takeSnapshot( context, gamePtr, gi );
gamePtr.release();
DBUtils.saveThumbnail( context, lock, thumb );
}
}
lock.unlock();
}
return thumb;
}
@ -504,9 +512,10 @@ public class GameUtils {
CurGameInfo gi, long groupID )
{
byte[] stream = XwJNI.game_saveToStream( gamePtr, gi );
GameLock lock = DBUtils.saveNewGame( context, stream, groupID, null );
long rowid = lock.getRowid();
lock.unlock();
long rowid;
try ( GameLock lock = DBUtils.saveNewGame( context, stream, groupID, null ) ) {
rowid = lock.getRowid();
}
return rowid;
}
@ -537,10 +546,10 @@ public class GameUtils {
long rowid = DBUtils.ROWID_NOTFOUND;
byte[] bytes = XwJNI.gi_to_stream( gi );
if ( null != bytes ) {
GameLock lock = DBUtils.saveNewGame( context, bytes, groupID,
gameName );
rowid = lock.getRowid();
lock.unlock();
try ( GameLock lock = DBUtils.saveNewGame( context, bytes, groupID,
gameName ) ) {
rowid = lock.getRowid();
}
}
return rowid;
}
@ -638,65 +647,17 @@ public class GameUtils {
}
if ( DBUtils.ROWID_NOTFOUND != rowid ) {
GameLock lock = new GameLock( rowid, true ).lock();
applyChanges( context, sink, gi, util, addr, null, lock, false );
lock.unlock();
// Use tryLock in case we're on UI thread. It's guaranteed to
// succeed because we just created the rowid.
try ( GameLock lock = GameLock.getFor( rowid ).tryLock() ) {
Assert.assertNotNull( lock );
applyChanges( context, sink, gi, util, addr, null, lock, false );
}
}
return rowid;
}
public static long makeNewGame( Context context, MultiMsgSink sink,
int gameID, CommsAddrRec addr, int lang,
String dict, int nPlayersT,
int nPlayersH, int forceChannel,
String gameName )
{
return makeNewGame( context, sink, DBUtils.GROUPID_UNSPEC, gameID, addr,
lang, dict, nPlayersT, nPlayersH, forceChannel,
gameName );
}
public static long makeNewGame( Context context, int gameID,
CommsAddrRec addr, int lang,
String dict, int nPlayersT,
int nPlayersH, int forceChannel,
String gameName )
{
return makeNewGame( context, DBUtils.GROUPID_UNSPEC, gameID, addr,
lang, dict, nPlayersT, nPlayersH, forceChannel,
gameName );
}
public static long makeNewGame( Context context, long groupID, int gameID,
CommsAddrRec addr, int lang, String dict,
int nPlayersT, int nPlayersH,
int forceChannel, String gameName )
{
return makeNewGame( context, null, groupID, gameID, addr, lang, dict,
nPlayersT, nPlayersH, forceChannel, gameName );
}
public static long makeNewGame( Context context, MultiMsgSink sink,
long groupID, int gameID, CommsAddrRec addr,
int lang, String dict,
int nPlayersT, int nPlayersH,
int forceChannel, String gameName )
{
long rowid = DBUtils.ROWID_NOTFOUND;
int[] langa = { lang };
String[] dicta = { dict };
boolean isHost = null == addr;
if ( isHost ) {
addr = new CommsAddrRec( null, null );
}
String inviteID = GameUtils.formatGameID( gameID );
return makeNewMultiGame( context, sink, (UtilCtxt)null, groupID, addr,
langa, dicta, null, nPlayersT, nPlayersH,
forceChannel, inviteID, gameID, gameName,
isHost );
}
// @SuppressLint({ "NewApi", "NewApi", "NewApi", "NewApi" })
// @SuppressWarnings("deprecation")
// @TargetApi(11)
@ -865,7 +826,7 @@ public class GameUtils {
return file.endsWith( XWConstants.GAME_EXTN );
}
public static Bundle makeLaunchExtras( long rowid, boolean invited )
private static Bundle makeLaunchExtras( long rowid, boolean invited )
{
Bundle bundle = new Bundle();
bundle.putLong( INTENT_KEY_ROWID, rowid );
@ -949,54 +910,57 @@ public class GameUtils {
// have the lock and we'll never get it. Better to drop
// the message than fire the hung-lock assert. Messages
// belong in local pre-delivery storage anyway.
GameLock lock = new GameLock( rowid, true ).lock( 150 );
if ( null != lock ) {
CurGameInfo gi = new CurGameInfo( context );
FeedUtilsImpl feedImpl = new FeedUtilsImpl( context, rowid );
GamePtr gamePtr = loadMakeGame( context, gi, feedImpl, sink, lock );
if ( null != gamePtr ) {
XwJNI.comms_resendAll( gamePtr, false, false );
try ( GameLock lock = GameLock.getFor( rowid ).lock( 150 ) ) {
if ( null != lock ) {
CurGameInfo gi = new CurGameInfo( context );
FeedUtilsImpl feedImpl = new FeedUtilsImpl( context, rowid );
GamePtr gamePtr = loadMakeGame( context, gi, feedImpl, sink, lock );
if ( null != gamePtr ) {
XwJNI.comms_resendAll( gamePtr, false, false );
Assert.assertNotNull( ret );
draw = XwJNI.game_receiveMessage( gamePtr, msg, ret );
XwJNI.comms_ackAny( gamePtr );
Assert.assertNotNull( ret );
draw = XwJNI.game_receiveMessage( gamePtr, msg, ret );
XwJNI.comms_ackAny( gamePtr );
// update gi to reflect changes due to messages
XwJNI.game_getGi( gamePtr, gi );
// update gi to reflect changes due to messages
XwJNI.game_getGi( gamePtr, gi );
if ( draw && XWPrefs.getThumbEnabled( context ) ) {
Bitmap bitmap = takeSnapshot( context, gamePtr, gi );
DBUtils.saveThumbnail( context, lock, bitmap );
}
if ( draw && XWPrefs.getThumbEnabled( context ) ) {
Bitmap bitmap = takeSnapshot( context, gamePtr, gi );
DBUtils.saveThumbnail( context, lock, bitmap );
}
if ( null != bmr ) {
if ( null != feedImpl.m_chat ) {
bmr.m_chat = feedImpl.m_chat;
bmr.m_chatFrom = feedImpl.m_chatFrom;
bmr.m_chatTs = feedImpl.m_ts;
} else {
LastMoveInfo lmi = new LastMoveInfo();
XwJNI.model_getPlayersLastScore( gamePtr, -1, lmi );
bmr.m_lmi = lmi;
if ( null != bmr ) {
if ( null != feedImpl.m_chat ) {
bmr.m_chat = feedImpl.m_chat;
bmr.m_chatFrom = feedImpl.m_chatFrom;
bmr.m_chatTs = feedImpl.m_ts;
} else {
LastMoveInfo lmi = new LastMoveInfo();
XwJNI.model_getPlayersLastScore( gamePtr, -1, lmi );
bmr.m_lmi = lmi;
}
}
saveGame( context, gamePtr, gi, lock, false );
GameSummary summary = summarizeAndRelease( context, lock,
gamePtr, gi );
if ( null != isLocalOut ) {
isLocalOut[0] = 0 <= summary.turn
&& gi.players[summary.turn].isLocal;
}
int flags = setFromFeedImpl( feedImpl );
if ( GameSummary.MSG_FLAGS_NONE != flags ) {
draw = true;
int curFlags = DBUtils.getMsgFlags( context, rowid );
DBUtils.setMsgFlags( rowid, flags | curFlags );
}
}
saveGame( context, gamePtr, gi, lock, false );
GameSummary summary = summarizeAndRelease( context, lock,
gamePtr, gi );
if ( null != isLocalOut ) {
isLocalOut[0] = 0 <= summary.turn
&& gi.players[summary.turn].isLocal;
}
int flags = setFromFeedImpl( feedImpl );
if ( GameSummary.MSG_FLAGS_NONE != flags ) {
draw = true;
int curFlags = DBUtils.getMsgFlags( context, rowid );
DBUtils.setMsgFlags( rowid, flags | curFlags );
}
}
lock.unlock();
} catch ( GameLock.GameLockedException gle ) {
DbgUtils.toastNoLock( TAG, context, "feedMessage(): dropping message "
+ " for %d", rowid );
}
}
return draw;
@ -1008,35 +972,37 @@ public class GameUtils {
public static boolean replaceDicts( Context context, long rowid,
String oldDict, String newDict )
{
GameLock lock = new GameLock( rowid, true ).lock(300);
boolean success = null != lock;
if ( success ) {
byte[] stream = savedGame( context, lock );
CurGameInfo gi = new CurGameInfo( context );
XwJNI.gi_from_stream( gi, stream );
boolean success;
try ( GameLock lock = GameLock.getFor( rowid ).lock(300) ) {
success = null != lock;
if ( success ) {
byte[] stream = savedGame( context, lock );
CurGameInfo gi = new CurGameInfo( context );
XwJNI.gi_from_stream( gi, stream );
// first time required so dictNames() will work
gi.replaceDicts( context, newDict );
// first time required so dictNames() will work
gi.replaceDicts( context, newDict );
String[] dictNames = gi.dictNames();
DictUtils.DictPairs pairs = DictUtils.openDicts( context,
dictNames );
String[] dictNames = gi.dictNames();
DictUtils.DictPairs pairs = DictUtils.openDicts( context,
dictNames );
GamePtr gamePtr =
XwJNI.initFromStream( rowid, stream, gi, dictNames,
pairs.m_bytes, pairs.m_paths,
gi.langName( context ), null,
null, CommonPrefs.get( context ), null );
// second time required as game_makeFromStream can overwrite
gi.replaceDicts( context, newDict );
GamePtr gamePtr =
XwJNI.initFromStream( rowid, stream, gi, dictNames,
pairs.m_bytes, pairs.m_paths,
gi.langName( context ), null,
null, CommonPrefs.get( context ), null );
// second time required as game_makeFromStream can overwrite
gi.replaceDicts( context, newDict );
saveGame( context, gamePtr, gi, lock, false );
saveGame( context, gamePtr, gi, lock, false );
summarizeAndRelease( context, lock, gamePtr, gi );
summarizeAndRelease( context, lock, gamePtr, gi );
lock.unlock();
} else {
Log.w( TAG, "replaceDicts: unable to open rowid %d", rowid );
} else {
DbgUtils.toastNoLock( TAG, context, "replaceDicts(): rowid %d",
rowid );
}
}
return success;
} // replaceDicts
@ -1292,32 +1258,32 @@ public class GameUtils {
}
}
GameLock lock = new GameLock( rowid, false );
if ( lock.tryLock() ) {
CurGameInfo gi = new CurGameInfo( m_context );
MultiMsgSink sink = new MultiMsgSink( m_context, rowid );
GamePtr gamePtr = loadMakeGame( m_context, gi, sink, lock );
if ( null != gamePtr ) {
int nSent = XwJNI.comms_resendAll( gamePtr, true,
m_filter, false );
gamePtr.release();
Log.d( TAG, "Resender.doInBackground(): sent %d "
+ "messages for rowid %d", nSent, rowid );
nSentTotal += sink.numSent();
try ( GameLock lock = GameLock.getFor( rowid ).tryLockRO() ) {
if ( null != lock ) {
CurGameInfo gi = new CurGameInfo( m_context );
MultiMsgSink sink = new MultiMsgSink( m_context, rowid );
GamePtr gamePtr = loadMakeGame( m_context, gi, sink, lock );
if ( null != gamePtr ) {
int nSent = XwJNI.comms_resendAll( gamePtr, true,
m_filter, false );
gamePtr.release();
Log.d( TAG, "Resender.doInBackground(): sent %d "
+ "messages for rowid %d", nSent, rowid );
nSentTotal += sink.numSent();
} else {
Log.d( TAG, "Resender.doInBackground(): loadMakeGame()"
+ " failed for rowid %d", rowid );
}
} else {
Log.d( TAG, "Resender.doInBackground(): loadMakeGame()"
+ " failed for rowid %d", rowid );
}
lock.unlock();
} else {
JNIThread jniThread = JNIThread.getRetained( rowid, false );
if ( null != jniThread ) {
jniThread.handle( JNIThread.JNICmd.CMD_RESEND, false,
false, false );
jniThread.release();
} else {
Log.w( TAG, "Resender.doInBackground: unable to unlock %d",
rowid );
JNIThread jniThread = JNIThread.getRetained( m_context, rowid );
if ( null != jniThread ) {
jniThread.handle( JNIThread.JNICmd.CMD_RESEND, false,
false, false );
jniThread.release();
} else {
Log.w( TAG, "Resender.doInBackground: unable to unlock %d",
rowid );
}
}
}
}

View file

@ -44,7 +44,6 @@ import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.TextView;
import junit.framework.Assert;
import org.eehouse.android.xw4.DBUtils.GameChangeType;
import org.eehouse.android.xw4.DBUtils.GameGroupInfo;
@ -86,6 +85,7 @@ public class GamesListDelegate extends ListDelegateBase
private static final String RELAYIDS_EXTRA = "relayids";
private static final String ROWID_EXTRA = "rowid";
private static final String BACKGROUND_EXTRA = "bkgrnd";
private static final String GAMEID_EXTRA = "gameid";
private static final String REMATCH_ROWID_EXTRA = "rm_rowid";
private static final String REMATCH_DICT_EXTRA = "rm_dict";
@ -987,6 +987,16 @@ public class GamesListDelegate extends ListDelegateBase
} );
updateField();
// RECEIVE_SMS is required now (Oreo/SDK_26) but wasn't
// before. There's logic elsewhere to ask for it AND SEND_SMS, but if
// the user's already granted SEND_SMS we can get RECEIVE_SMS just by
// asking (OS will grant without user interaction) since they're in
// the same group. So just do it now. This code can be removed
// later...
if ( Perms23.havePermission( Perm.SEND_SMS ) ) {
Perms23.tryGetPerms( this, Perm.RECEIVE_SMS, 0, Action.SKIP_CALLBACK );
}
} // init
@Override
@ -1221,14 +1231,14 @@ public class GamesListDelegate extends ListDelegateBase
final Object ... args )
{
switch( event ) {
case HOST_PONGED:
post( new Runnable() {
public void run() {
DbgUtils.showf( m_activity,
"Pong from %s", args[0].toString() );
}
});
break;
// case HOST_PONGED:
// post( new Runnable() {
// public void run() {
// DbgUtils.showf( m_activity,
// "Pong from %s", args[0].toString() );
// }
// });
// break;
case BT_GAME_CREATED:
post( new Runnable() {
public void run() {
@ -1661,9 +1671,11 @@ public class GamesListDelegate extends ListDelegateBase
enable = BoardDelegate.rematchSupported( m_activity, rowID );
Utils.setItemVisible( menu, R.id.games_game_rematch, enable );
enable = item.getSummary().isMultiGame()
boolean isMultiGame = item.getSummary().isMultiGame();
enable = isMultiGame
&& (BuildConfig.DEBUG || XWPrefs.getDebugEnabled( m_activity ));
Utils.setItemVisible( menu, R.id.games_game_invites, enable );
Utils.setItemVisible( menu, R.id.games_game_netstats, isMultiGame );
enable = !m_launchedGames.contains( rowID );
Utils.setItemVisible( menu, R.id.games_game_delete, enable );
@ -1767,7 +1779,7 @@ public class GamesListDelegate extends ListDelegateBase
return handled;
}
private boolean handleSelGamesItem( int itemID, final long[] selRowIDs )
private boolean handleSelGamesItem( int itemID, long[] selRowIDs )
{
boolean handled = true;
boolean dropSels = false;
@ -1806,8 +1818,9 @@ public class GamesListDelegate extends ListDelegateBase
.show();
break;
case R.id.games_game_copy:
final long selRowID = selRowIDs[0];
final GameSummary smry = GameUtils.getSummary( m_activity,
selRowIDs[0] );
selRowID );
if ( smry.inRelayGame() ) {
makeOkOnlyBuilder( R.string.no_copy_network ).show();
} else {
@ -1816,14 +1829,14 @@ public class GamesListDelegate extends ListDelegateBase
public void run() {
Activity self = m_activity;
byte[] stream =
GameUtils.savedGame( self, selRowIDs[0] );
GameUtils.savedGame( self, selRowID );
long groupID = XWPrefs
.getDefaultNewGameGroup( self );
GameLock lock =
GameUtils.saveNewGame( self, stream, groupID );
DBUtils.saveSummary( self, lock, smry );
m_mySIS.selGames.add( lock.getRowid() );
lock.unlock();
try ( GameLock lock =
GameUtils.saveNewGame( self, stream, groupID ) ) {
DBUtils.saveSummary( self, lock, smry );
m_mySIS.selGames.add( lock.getRowid() );
}
mkListAdapter();
}
});
@ -1838,6 +1851,10 @@ public class GamesListDelegate extends ListDelegateBase
showDialogFragment( DlgID.RENAME_GAME, selRowIDs[0] );
break;
case R.id.games_game_netstats:
onStatusClicked( selRowIDs[0] );
break;
// DEBUG only
case R.id.games_game_invites:
msg = GameUtils.getSummary( m_activity, selRowIDs[0] )
@ -2023,9 +2040,7 @@ public class GamesListDelegate extends ListDelegateBase
try {
hasDicts = GameUtils.gameDictsHere( m_activity, rowid, missingNames,
missingLang );
} catch ( GameUtils.NoSuchGameException nsge ) {
hasDicts = true; // irrelevant question
} catch ( GameLock.GameLockedException gle ) {
} catch ( GameLock.GameLockedException | GameUtils.NoSuchGameException ex ) {
hasDicts = true; // irrelevant question
}
@ -2096,7 +2111,8 @@ public class GamesListDelegate extends ListDelegateBase
boolean haveDict;
try {
haveDict = GameUtils.gameDictsHere( m_activity, rowid );
} catch ( GameLock.GameLockedException gle ) {
} catch ( GameLock.GameLockedException
| GameUtils.NoSuchGameException gle ) {
haveDict = true;
}
if ( haveDict ) {
@ -2237,7 +2253,12 @@ public class GamesListDelegate extends ListDelegateBase
int lang = extras.getInt( REMATCH_LANG_EXTRA, -1 );
String json = extras.getString( REMATCH_PREFS_EXTRA );
// Don't save rematch in Archive group
long groupID = DBUtils.getGroupForGame( m_activity, srcRowID );
if ( groupID == DBUtils.getArchiveGroup( m_activity ) ) {
groupID = XWPrefs.getDefaultNewGameGroup( m_activity );
}
newid = GameUtils.makeNewMultiGame( m_activity, groupID, dict,
lang, json, addrs, gameName );
DBUtils.addRematchInfo( m_activity, newid, btAddr, phone,
@ -2267,6 +2288,14 @@ public class GamesListDelegate extends ListDelegateBase
}
}
private void tryBackgroundIntent( Intent intent )
{
if ( intent.getBooleanExtra( BACKGROUND_EXTRA, false ) ) {
makeOkOnlyBuilder( R.string.btservice_expl )
.show();
}
}
private void askDefaultName()
{
String name = CommonPrefs.getDefaultPlayerName( m_activity, 0, true );
@ -2481,6 +2510,7 @@ public class GamesListDelegate extends ListDelegateBase
startRematch( intent );
tryAlert( intent );
tryNFCIntent( intent );
tryBackgroundIntent( intent );
}
private void doOpenGame( Object[] params )
@ -2665,6 +2695,13 @@ public class GamesListDelegate extends ListDelegateBase
return intent;
}
public static Intent makeBackgroundIntent( Context context )
{
Intent intent = makeSelfIntent( context );
intent.putExtra( BACKGROUND_EXTRA, true );
return intent;
}
public static Intent makeRowidIntent( Context context, long rowid )
{
Intent intent = makeSelfIntent( context );

View file

@ -27,7 +27,6 @@ import android.widget.ImageButton;
import android.widget.LinearLayout;
import android.widget.TextView;
import junit.framework.Assert;
import org.eehouse.android.xw4.loc.LocUtils;

View file

@ -26,7 +26,6 @@ import android.content.Intent;
import org.eehouse.android.xw4.DlgDelegate.Action;
import junit.framework.Assert;
class HostDelegate extends DelegateBase {
private static final String ACTION = "ACTION";

View file

@ -31,7 +31,6 @@ import android.widget.Button;
import java.util.ArrayList;
import junit.framework.Assert;
import org.eehouse.android.xw4.DlgDelegate.Action;
import org.eehouse.android.xw4.DlgDelegate.ActionPair;

View file

@ -36,14 +36,17 @@ import android.widget.FrameLayout;
import android.widget.ListView;
import android.widget.Spinner;
import android.widget.TextView;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import junit.framework.Assert;
abstract class InviteDelegate extends ListDelegateBase
implements View.OnClickListener,
ViewGroup.OnHierarchyChangeListener {
@ -54,34 +57,25 @@ abstract class InviteDelegate extends ListDelegateBase
String getDev(); // the string that identifies this item in results
}
protected static class TwoStringPair implements InviterItem {
public String str1;
protected static class TwoStringPair implements InviterItem, Serializable {
private String mDev;
public String str2;
public TwoStringPair( String str1, String str2 ) {
this.str1 = str1; this.str2 = str2;
public TwoStringPair( String dev, String str2 ) {
mDev = dev; this.str2 = str2;
}
public static TwoStringPair[] make( String[] names, String[] addrs )
{
TwoStringPair[] pairs = new TwoStringPair[names.length];
for ( int ii = 0; ii < pairs.length; ++ii ) {
pairs[ii] = new TwoStringPair( names[ii], addrs[ii] );
}
return pairs;
}
public String getDev() { return str1; }
public String getDev() { return mDev; }
public boolean equals( InviterItem item )
{
boolean result = false;
if ( null != item ) {
TwoStringPair pair = (TwoStringPair)item;
result = str1.equals( pair.str1 )
result = mDev.equals( pair.mDev )
&& ((null == str2 && null == pair.str2)
|| str2.equals( pair.str2 ) );
Log.d( TAG, "%s.equals(%s) => %b", str1, pair.str1, result );
Log.d( TAG, "%s.equals(%s) => %b", mDev, pair.mDev, result );
}
return result;
}
@ -153,14 +147,17 @@ abstract class InviteDelegate extends ListDelegateBase
for ( int id : buttonBarItemIds ) {
bar.findViewById( id ).setOnClickListener( listener );
}
tryEnable();
}
protected void updateListAdapter( InviterItem[] items )
protected void updateListAdapter( List<? extends InviterItem> items )
{
updateListAdapter( R.layout.two_strs_item, items );
}
protected void updateListAdapter( int itemId, InviterItem[] items )
protected void updateListAdapter( int itemId,
List<? extends InviterItem> items )
{
updateChecked( items );
m_adapter = new InviteItemsAdapter( itemId, items );
@ -174,13 +171,7 @@ abstract class InviteDelegate extends ListDelegateBase
}
}
protected void onBarButtonClicked( int id )
{
Assert.fail(); // subclass must implement
}
// Subclasses can do something here
protected void addToButtonBar( FrameLayout container ) {}
abstract void onBarButtonClicked( int id );
////////////////////////////////////////
// View.OnClickListener
@ -246,7 +237,7 @@ abstract class InviteDelegate extends ListDelegateBase
protected void clearChecked() { m_checked.clear(); }
// Figure which previously-checked items belong in the new set.
private void updateChecked( InviterItem[] newItems )
private void updateChecked( List<? extends InviterItem> newItems )
{
Set<InviterItem> old = new HashSet<InviterItem>();
old.addAll( m_checked );
@ -282,11 +273,13 @@ abstract class InviteDelegate extends ListDelegateBase
private InviterItem[] m_items;
private int m_itemId;
public InviteItemsAdapter( int itemID, InviterItem[] items )
public InviteItemsAdapter( int itemID, List<? extends InviterItem> items )
{
super( null == items? 0 : items.length );
super( null == items? 0 : items.size() );
m_itemId = itemID;
m_items = items;
if ( null != items ) {
m_items = items.toArray( new InviterItem[items.size()] );
}
// m_items = new LinearLayout[getCount()];
}

View file

@ -23,21 +23,23 @@ package org.eehouse.android.xw4;
import java.util.Formatter;
public class Log {
private static final String PRE_TAG = BuildConfig.FLAVOR + "-";
public static void d( String tag, String fmt, Object... args ) {
String str = new Formatter().format( fmt, args ).toString();
android.util.Log.d( tag, str );
android.util.Log.d( PRE_TAG + tag, str );
}
public static void w( String tag, String fmt, Object... args ) {
String str = new Formatter().format( fmt, args ).toString();
android.util.Log.w( tag, str );
android.util.Log.w( PRE_TAG + tag, str );
}
public static void e( String tag, String fmt, Object... args ) {
String str = new Formatter().format( fmt, args ).toString();
android.util.Log.e( tag, str );
android.util.Log.e( PRE_TAG + tag, str );
}
public static void i( String tag, String fmt, Object... args ) {
String str = new Formatter().format( fmt, args ).toString();
android.util.Log.i( tag, str );
android.util.Log.i( PRE_TAG + tag, str );
}
public static void ex( String tag, Exception exception )
@ -45,5 +47,4 @@ public class Log {
w( tag, "Exception: %s", exception.toString() );
DbgUtils.printStack( tag, exception.getStackTrace() );
}
}

View file

@ -26,7 +26,6 @@ import android.os.Bundle;
import android.support.v4.app.DialogFragment;
import java.io.Serializable;
import junit.framework.Assert;
import org.eehouse.android.xw4.loc.LocUtils;

View file

@ -36,7 +36,6 @@ import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.TextView;
import junit.framework.Assert;
import org.eehouse.android.xw4.loc.LocUtils;

View file

@ -35,7 +35,6 @@ import android.view.MenuItem;
import android.view.View;
import android.widget.LinearLayout;
import junit.framework.Assert;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
@ -138,8 +137,8 @@ public class MainActivity extends XWActivity
}
} );
if ( BuildConfig.DEBUG ) {
DbgUtils.showf( this, "Putting off handling intent; %d waiting",
m_runWhenSafe.size() );
Log.d( TAG, "Putting off handling intent; %d waiting",
m_runWhenSafe.size() );
}
handled = true;
}

View file

@ -22,7 +22,6 @@ package org.eehouse.android.xw4;
import android.content.Context;
import junit.framework.Assert;
import org.eehouse.android.xw4.jni.CommsAddrRec;
import org.eehouse.android.xw4.jni.CommsAddrRec.CommsConnType;

View file

@ -26,7 +26,6 @@ import android.content.DialogInterface.OnClickListener;
import android.content.Intent;
import android.os.Bundle;
import junit.framework.Assert;
import org.eehouse.android.xw4.loc.LocUtils;
@ -88,7 +87,6 @@ public class MultiService {
SMS_SEND_FAILED_NOPERMISSION,
BT_GAME_CREATED,
BT_ERR_COUNT,
RELAY_ALERT,
};

View file

@ -27,7 +27,6 @@ import android.content.Context;
import android.content.Intent;
import android.text.TextUtils;
import junit.framework.Assert;
import org.eehouse.android.xw4.DBUtils.NeedsNagInfo;
import org.eehouse.android.xw4.jni.GameSummary;

View file

@ -39,7 +39,6 @@ import java.util.Iterator;
import org.json.JSONException;
import org.json.JSONObject;
import junit.framework.Assert;
import org.eehouse.android.xw4.jni.CommsAddrRec.CommsConnType;
import org.eehouse.android.xw4.jni.CommsAddrRec.CommsConnTypeSet;

View file

@ -29,7 +29,6 @@ import android.net.NetworkInfo;
import android.os.Build;
import android.os.Handler;
import junit.framework.Assert;
import org.eehouse.android.xw4.jni.CommsAddrRec.CommsConnType;
@ -54,17 +53,21 @@ public class NetStateCache {
public static void register( Context context, StateChangedIf proc )
{
initIfNot( context );
synchronized( s_ifs ) {
s_ifs.add( proc );
if ( Utils.isOnUIThread() ) {
initIfNot( context );
synchronized( s_ifs ) {
s_ifs.add( proc );
}
}
}
public static void unregister( Context context, StateChangedIf proc )
{
initIfNot( context );
synchronized( s_ifs ) {
s_ifs.remove( proc );
if ( Utils.isOnUIThread() ) {
initIfNot( context );
synchronized( s_ifs ) {
s_ifs.remove( proc );
}
}
}
@ -178,8 +181,6 @@ public class NetStateCache {
public PvtBroadcastReceiver()
{
DbgUtils.assertOnUIThread();
mHandler = new Handler();
mLastStateSent = s_netAvail;
}
@ -188,6 +189,11 @@ public class NetStateCache {
{
DbgUtils.assertOnUIThread();
if ( null == mHandler ) {
DbgUtils.assertOnUIThread();
mHandler = new Handler();
}
if ( intent.getAction().
equals( ConnectivityManager.CONNECTIVITY_ACTION)) {

View file

@ -23,7 +23,6 @@ package org.eehouse.android.xw4;
import android.content.Context;
import android.text.TextUtils;
import junit.framework.Assert;
import org.json.JSONArray;
import org.json.JSONException;
@ -276,8 +275,10 @@ public class NetUtils {
}
result = new String( bas.toByteArray() );
} else {
Log.w( TAG, "runConn: responseCode: %d for url: %s",
responseCode, conn.getURL() );
Log.w( TAG, "runConn: responseCode: %d/%s for url: %s",
responseCode, conn.getResponseMessage(),
conn.getURL() );
logErrorStream( conn.getErrorStream() );
}
} catch ( java.net.ProtocolException pe ) {
Log.ex( TAG, pe );
@ -289,6 +290,24 @@ public class NetUtils {
return result;
}
private static void logErrorStream( InputStream is )
{
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
for ( ; ; ) {
int length = is.read( buffer );
if ( length == -1 ) {
break;
}
baos.write( buffer, 0, length );
}
Log.e( TAG, baos.toString() );
} catch (Exception ex) {
Log.e( TAG, ex.getMessage() );
}
}
// This handles multiple params but only every gets passed one!
private static String getPostDataString( Map<String, String> params )
{

View file

@ -30,7 +30,6 @@ import android.support.v4.app.DialogFragment;
import org.eehouse.android.xw4.DlgDelegate.Action;
import org.eehouse.android.xw4.DlgDelegate.ActionPair;
import junit.framework.Assert;
import org.eehouse.android.xw4.loc.LocUtils;

View file

@ -30,7 +30,6 @@ import android.support.v4.app.DialogFragment;
import org.eehouse.android.xw4.DlgDelegate.Action;
import org.eehouse.android.xw4.DlgDelegate.ActionPair;
import junit.framework.Assert;
import org.eehouse.android.xw4.loc.LocUtils;

View file

@ -30,10 +30,18 @@ public class OnBootReceiver extends BroadcastReceiver {
@Override
public void onReceive( Context context, Intent intent )
{
if ( null != intent && null != intent.getAction()
&& intent.getAction().equals( Intent.ACTION_BOOT_COMPLETED ) ) {
Log.d( TAG, "got ACTION_BOOT_COMPLETED" );
startTimers( context );
if ( null != intent ) {
String action = intent.getAction();
Log.d( TAG, "got %s", action );
switch( action ) {
case Intent.ACTION_BOOT_COMPLETED:
case Intent.ACTION_MY_PACKAGE_REPLACED:
startTimers( context );
// Let's not put up the foreground service notification on
// boot. Too likely to annoy.
// BTService.onAppToBackground( context );
break;
}
}
}

View file

@ -19,6 +19,7 @@
package org.eehouse.android.xw4;
import android.Manifest;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
@ -30,6 +31,7 @@ import android.support.v4.content.ContextCompat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
@ -38,20 +40,20 @@ import org.eehouse.android.xw4.DlgDelegate.DlgClickNotify;
import org.eehouse.android.xw4.jni.CommsAddrRec.CommsConnType;
import org.eehouse.android.xw4.loc.LocUtils;
import junit.framework.Assert;
public class Perms23 {
private static final String TAG = Perms23.class.getSimpleName();
public static enum Perm {
READ_PHONE_STATE("android.permission.READ_PHONE_STATE"),
STORAGE("android.permission.WRITE_EXTERNAL_STORAGE"),
SEND_SMS("android.permission.SEND_SMS"),
READ_CONTACTS("android.permission.READ_CONTACTS")
;
READ_PHONE_STATE(Manifest.permission.READ_PHONE_STATE),
STORAGE(Manifest.permission.WRITE_EXTERNAL_STORAGE),
SEND_SMS(Manifest.permission.SEND_SMS),
RECEIVE_SMS(Manifest.permission.RECEIVE_SMS),
READ_CONTACTS(Manifest.permission.READ_CONTACTS);
private String m_str;
private Perm(String str) { m_str = str; }
public String getString() { return m_str; }
public static Perm getFor( String str ) {
Perm result = null;
@ -80,6 +82,12 @@ public class Perms23 {
m_perms.addAll( perms );
}
public Builder( Perm[] perms ) {
for ( Perm perm : perms ) {
m_perms.add( perm );
}
}
public Builder( Perm perm ) {
m_perms.add( perm );
}
@ -128,7 +136,7 @@ public class Perms23 {
if ( haveAll ) {
if ( null != cbck ) {
Map<Perm, Boolean> map = new HashMap<Perm, Boolean>();
Map<Perm, Boolean> map = new HashMap<>();
for ( Perm perm : m_perms ) {
map.put( perm, true );
}
@ -149,36 +157,37 @@ public class Perms23 {
private static class QueryInfo {
private Action m_action;
private Perm m_perm;
private Perm[] m_perms;
private DelegateBase m_delegate;
private String m_rationaleMsg;
private Object[] m_params;
private QueryInfo( DelegateBase delegate, Action action,
Perm perm, String msg, Object[] params ) {
Perm[] perms, String msg, Object[] params ) {
m_delegate = delegate;
m_action = action;
m_perm = perm;
m_perms = perms;
m_rationaleMsg = msg;
m_params = params;
}
private QueryInfo( DelegateBase delegate, Object[] params )
{
this( delegate, (Action)params[0], (Perm)params[1], (String)params[2],
this( delegate, (Action)params[0], (Perm[])params[1], (String)params[2],
(Object[])params[3] );
}
private Object[] getParams()
{
return new Object[] { m_action, m_perm, m_rationaleMsg, m_params };
return new Object[] { m_action, m_perms, m_rationaleMsg, m_params };
}
private void doIt( boolean showRationale )
{
Builder builder = new Builder( m_perm );
Builder builder = new Builder( m_perms );
if ( showRationale && null != m_rationaleMsg ) {
builder.setOnShowRationale( new OnShowRationale() {
@Override
public void onShouldShowRationale( Set<Perm> perms ) {
m_delegate.makeConfirmThenBuilder( m_rationaleMsg,
Action.PERMS_QUERY )
@ -191,9 +200,21 @@ public class Perms23 {
} );
}
builder.asyncQuery( m_delegate.getActivity(), new PermCbck() {
@Override
public void onPermissionResult( Map<Perm, Boolean> perms ) {
if ( Action.SKIP_CALLBACK != m_action ) {
if ( perms.get( m_perm ) ) {
Set<Perm> keys = perms.keySet();
// We need all the sought perms to have been granted
boolean allGood = keys.size() == m_params.length;
for ( Iterator<Perm> iter = keys.iterator();
allGood && iter.hasNext(); ) {
if ( !perms.get(iter.next()) ) {
allGood = false;
}
}
if ( allGood ) {
m_delegate.onPosButton( m_action, m_params );
} else {
m_delegate.onNegButton( m_action, m_params );
@ -231,22 +252,36 @@ public class Perms23 {
* Request permissions, giving rationale once, then call with action and
* either positive or negative, the former if permission granted.
*/
public static void tryGetPerms( DelegateBase delegate, Perm perm, int rationaleId,
public static void tryGetPerms( DelegateBase delegate, Perm[] perms, int rationaleId,
final Action action, Object... params )
{
// Log.d( TAG, "tryGetPerms(%s)", perm.toString() );
Context context = XWApp.getContext();
String msg = LocUtils.getString( context, rationaleId );
tryGetPerms( delegate, perm, msg, action, params );
String msg = rationaleId == 0
? null : LocUtils.getString( context, rationaleId );
tryGetPerms( delegate, perms, msg, action, params );
}
public static void tryGetPerms( DelegateBase delegate, Perm[] perms,
String rationaleMsg, final Action action,
Object... params )
{
// Log.d( TAG, "tryGetPerms(%s)", perm.toString() );
new QueryInfo( delegate, action, perms, rationaleMsg, params )
.doIt( true );
}
public static void tryGetPerms( DelegateBase delegate, Perm perm,
String rationaleMsg, final Action action,
Object... params )
{
// Log.d( TAG, "tryGetPerms(%s)", perm.toString() );
new QueryInfo( delegate, action, perm, rationaleMsg, params )
.doIt( true );
tryGetPerms( delegate, new Perm[]{ perm }, rationaleMsg, action, params );
}
public static void tryGetPerms( DelegateBase delegate, Perm perm, int rationaleId,
final Action action, Object... params )
{
tryGetPerms( delegate, new Perm[]{perm}, rationaleId, action, params );
}
public static void onGotPermsAction( DelegateBase delegate, boolean positive,
@ -262,6 +297,7 @@ public class Perms23 {
{
// Log.d( TAG, "gotPermissionResult(%s)", perms.toString() );
Map<Perm, Boolean> result = new HashMap<Perm, Boolean>();
boolean shouldResend = false;
for ( int ii = 0; ii < perms.length; ++ii ) {
Perm perm = Perm.getFor( perms[ii] );
boolean granted = PackageManager.PERMISSION_GRANTED == granteds[ii];
@ -270,9 +306,8 @@ public class Perms23 {
// Hack. If SMS has been granted, resend all moves. This should be
// replaced with an api allowing listeners to register
// Perm-by-Perm, but I'm in a hurry.
if ( granted && perm == Perm.SEND_SMS ) {
GameUtils.resendAllIf( context, CommsConnType.COMMS_CONN_SMS,
true, true );
if ( granted && (perm == Perm.SEND_SMS || perm == Perm.RECEIVE_SMS) ) {
shouldResend = true;
}
// Log.d( TAG, "calling %s.onPermissionResult(%s, %b)",
@ -280,6 +315,11 @@ public class Perms23 {
// granted );
}
if ( shouldResend ) {
GameUtils.resendAllIf( context, CommsConnType.COMMS_CONN_SMS,
true, true );
}
PermCbck cbck = s_map.remove( code );
if ( null != cbck ) {
cbck.onPermissionResult( result );
@ -291,15 +331,15 @@ public class Perms23 {
String permString = perm.getString();
boolean result = PackageManager.PERMISSION_GRANTED
== ContextCompat.checkSelfPermission( XWApp.getContext(), permString );
// Log.d( TAG, "havePermission(%s) => %b", permString, result );
return result;
}
// This is probably overkill as the OS only allows one permission request
// at a time
// If two permission requests are made in a row the map may contain more
// than one entry.
private static int s_nextRecord = 0;
private static int register( PermCbck cbck )
{
Assert.assertTrue( !BuildConfig.DEBUG || 0 == s_map.size() );
DbgUtils.assertOnUIThread();
int code = ++s_nextRecord;
s_map.put( code, cbck );

View file

@ -27,7 +27,6 @@ import android.os.Bundle;
import android.preference.PreferenceActivity;
import android.support.v4.app.DialogFragment;
import junit.framework.Assert;
import org.eehouse.android.xw4.DlgDelegate.Action;
import org.eehouse.android.xw4.DlgDelegate.ConfirmThenBuilder;

View file

@ -36,7 +36,6 @@ import android.preference.PreferenceScreen;
import android.view.View;
import android.widget.Button;
import junit.framework.Assert;
import org.eehouse.android.xw4.DlgDelegate.Action;
import org.eehouse.android.xw4.loc.LocUtils;

View file

@ -52,7 +52,6 @@ import java.util.Set;
import org.json.JSONArray;
import org.json.JSONObject;
import junit.framework.Assert;
import org.eehouse.android.xw4.DlgDelegate.Action;
@ -464,7 +463,7 @@ public class RelayInviteDelegate extends InviteDelegate {
});
addSelf();
updateListAdapter( m_devIDRecs.toArray( new DevIDRec[m_devIDRecs.size()] ) );
updateListAdapter( m_devIDRecs );
tryEnable();
}

View file

@ -25,10 +25,9 @@ import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.os.Handler;
import android.support.v4.app.JobIntentService;
import android.text.TextUtils;
import junit.framework.Assert;
import org.eehouse.android.xw4.GameUtils.BackMoveResult;
import org.eehouse.android.xw4.MultiService.DictFetchOwner;
import org.eehouse.android.xw4.MultiService.MultiEvent;
@ -63,7 +62,7 @@ import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
public class RelayService extends XWService
public class RelayService extends JobIntentService
implements NetStateCache.StateChangedIf {
private static final String TAG = RelayService.class.getSimpleName();
private static final int MAX_SEND = 1024;
@ -103,6 +102,7 @@ public class RelayService extends XWService
private static List<PacketData> s_packetsSentUDP = new ArrayList<>();
private static List<PacketData> s_packetsSentWeb = new ArrayList<>();
private static final PacketData sEOQPacket = new PacketData();
private static AtomicInteger s_nextPacketID = new AtomicInteger();
private static boolean s_gcmWorking = false;
private static boolean s_registered = false;
@ -113,13 +113,15 @@ public class RelayService extends XWService
static { resetBackoffTimer(); }
private Thread m_fetchThread = null; // no longer used
private AtomicReference<UDPThreads> m_UDPThreadsRef = new AtomicReference<>();
private static final AtomicReference<UDPThreads> sUDPThreadsRef = new AtomicReference<>();
private Handler m_handler;
private UDPThreads mThreads;
private Runnable m_onInactivity;
private int m_maxIntervalSeconds = 0;
private long m_lastGamePacketReceived;
private int m_nativeFailScore;
private boolean m_skipUPDSet;
private static AtomicInteger sNativeFailScore = new AtomicInteger();;
private static boolean sSkipUPDSet;
private RelayServiceHelper mHelper;
private static DevIDType s_curType = DevIDType.ID_TYPE_NONE;
private static long s_regStartTime = 0;
@ -190,15 +192,34 @@ public class RelayService extends XWService
public static void startService( Context context )
{
Log.i( TAG, "startService()" );
Intent intent = getIntentTo( context, MsgCmds.UDP_CHANGED );
context.startService( intent );
enqueueWork( context, intent );
}
// Must use the same lobID for all work enqueued for the same class
private final static int sJobID = RelayService.class.hashCode();
private static void enqueueWork( Context context, Intent intent )
{
Log.d( TAG, "calling enqueueWork(cmd=%s)", cmdFrom( intent ) );
enqueueWork( context, RelayService.class, sJobID, intent );
}
private static MsgCmds cmdFrom( Intent intent )
{
MsgCmds cmd;
try {
cmd = MsgCmds.values()[intent.getIntExtra( CMD_STR, -1 )];
} catch (Exception ex) { // OOB most likely
cmd = null;
}
return cmd;
}
private static void stopService( Context context )
{
Intent intent = getIntentTo( context, MsgCmds.STOP );
context.startService( intent );
enqueueWork( context, intent );
}
public static void inviteRemote( Context context, int destDevID,
@ -206,7 +227,7 @@ public class RelayService extends XWService
{
int myDevID = DevID.getRelayDevIDInt( context );
if ( 0 != myDevID ) {
context.startService( getIntentTo( context, MsgCmds.INVITE )
enqueueWork( context, getIntentTo( context, MsgCmds.INVITE )
.putExtra( DEV_ID_SRC, myDevID )
.putExtra( DEV_ID_DEST, destDevID )
.putExtra( RELAY_ID, relayID )
@ -217,13 +238,13 @@ public class RelayService extends XWService
public static void reset( Context context )
{
Intent intent = getIntentTo( context, MsgCmds.RESET );
context.startService( intent );
enqueueWork( context, intent );
}
public static void timerFired( Context context )
{
Intent intent = getIntentTo( context, MsgCmds.TIMER_FIRED );
context.startService( intent );
enqueueWork( context, intent );
}
public static int sendPacket( Context context, long rowid, byte[] msg )
@ -234,7 +255,7 @@ public class RelayService extends XWService
Intent intent = getIntentTo( context, MsgCmds.SEND )
.putExtra( ROWID, rowid )
.putExtra( BINBUFFER, msg );
context.startService( intent );
enqueueWork( context, intent );
result = msg.length;
} else {
Log.w( TAG, "sendPacket: network down" );
@ -251,7 +272,7 @@ public class RelayService extends XWService
.putExtra( ROWID, rowid )
.putExtra( RELAY_ID, relayID )
.putExtra( BINBUFFER, msg );
context.startService( intent );
enqueueWork( context, intent );
result = msg.length;
}
return result;
@ -266,18 +287,11 @@ public class RelayService extends XWService
{
Log.d( TAG, "receiveInvitation: got nli from %d: %s", srcDevID,
nli.toString() );
if ( !handleInvitation( nli, null, DictFetchOwner.OWNER_RELAY ) ) {
if ( !mHelper.handleInvitation( nli, null, DictFetchOwner.OWNER_RELAY ) ) {
Log.d( TAG, "handleInvitation() failed" );
}
}
@Override
void postNotification( String device, int gameID, long rowid )
{
String body = LocUtils.getString( this, R.string.new_relay_body );
GameUtils.postInvitedNotification( this, gameID, body, rowid );
}
// Exists to get incoming data onto the main thread
private static void postData( Context context, long rowid, byte[] msg )
{
@ -287,7 +301,7 @@ public class RelayService extends XWService
Intent intent = getIntentTo( context, MsgCmds.RECEIVE )
.putExtra( ROWID, rowid )
.putExtra( BINBUFFER, msg );
context.startService( intent );
enqueueWork( context, intent );
} else {
Log.w( TAG, "postData(): Dropping message for rowid %d:"
+ " not on device", rowid );
@ -305,33 +319,29 @@ public class RelayService extends XWService
Intent intent = getIntentTo( context, MsgCmds.PROCESS_GAME_MSGS )
.putExtra( MSGS_ARR, msgs64 )
.putExtra( RELAY_ID, relayId );
context.startService( intent );
enqueueWork( context, intent );
}
public static void processDevMsgs( Context context, String[] msgs64 )
{
Intent intent = getIntentTo( context, MsgCmds.PROCESS_DEV_MSGS )
.putExtra( MSGS_ARR, msgs64 );
context.startService( intent );
enqueueWork( context, intent );
}
private static Intent getIntentTo( Context context, MsgCmds cmd )
{
Intent intent = new Intent( context, RelayService.class );
intent.putExtra( CMD_STR, cmd.ordinal() );
Intent intent = new Intent( context, RelayService.class )
.putExtra( CMD_STR, cmd.ordinal() );
return intent;
}
@Override
protected MultiMsgSink getSink( long rowid )
{
return new RelayMsgSink().setRowID( rowid );
}
@Override
public void onCreate()
{
Log.d( TAG, "%s.onCreate()", this );
super.onCreate();
mHelper = new RelayServiceHelper( this );
m_lastGamePacketReceived =
XWPrefs.getPrefsLong( this, R.string.key_last_packet,
Utils.getCurSeconds() );
@ -339,7 +349,7 @@ public class RelayService extends XWService
m_handler = new Handler();
m_onInactivity = new Runnable() {
public void run() {
Log.d( TAG, "m_onInactivity fired" );
// Log.d( TAG, "m_onInactivity fired" );
if ( !shouldMaintainConnection() ) {
NetStateCache.unregister( RelayService.this,
RelayService.this );
@ -349,109 +359,20 @@ public class RelayService extends XWService
}
}
};
m_skipUPDSet = XWPrefs.getSkipToWebAPI( this );
mThreads = startUDPThreadsOnce();
if ( null == mThreads ) {
stopSelf();
}
sSkipUPDSet = XWPrefs.getSkipToWebAPI( this );
}
@Override
public int onStartCommand( Intent intent, int flags, int startId )
public void onHandleWork( Intent intent )
{
Integer result = null;
if ( null != intent ) {
MsgCmds cmd;
try {
cmd = MsgCmds.values()[intent.getIntExtra( CMD_STR, -1 )];
} catch (Exception ex) { // OOB most likely
cmd = null;
}
if ( null != cmd ) {
// Log.d( TAG, "onStartCommand(): cmd=%s", cmd.toString() );
switch( cmd ) {
case PROCESS_GAME_MSGS:
String[] relayIDs = new String[1];
relayIDs[0] = intent.getStringExtra( RELAY_ID );
long[] rowIDs = DBUtils.getRowIDsFor( this, relayIDs[0] );
if ( 0 < rowIDs.length ) {
byte[][][] msgs = expandMsgsArray( intent );
process( msgs, rowIDs, relayIDs );
}
break;
case PROCESS_DEV_MSGS:
byte[][][] msgss = expandMsgsArray( intent );
for ( byte[][] msgs : msgss ) {
for ( byte[] msg : msgs ) {
gotPacket( msg, true, false );
}
}
break;
case UDP_CHANGED:
startThreads();
break;
case RESET:
stopThreads();
startThreads();
break;
case UPGRADE:
UpdateCheckReceiver.checkVersions( this, false );
break;
case GOT_INVITE:
int srcDevID = intent.getIntExtra( INVITE_FROM, 0 );
NetLaunchInfo nli
= NetLaunchInfo.makeFrom( this, intent.getStringExtra(NLI_DATA) );
receiveInvitation( srcDevID, nli );
break;
case SEND:
case RECEIVE:
case SENDNOCONN:
startUDPThreadsIfNot();
long rowid = intent.getLongExtra( ROWID, -1 );
byte[] msg = intent.getByteArrayExtra( BINBUFFER );
if ( MsgCmds.SEND == cmd ) {
sendMessage( rowid, msg );
} else if ( MsgCmds.SENDNOCONN == cmd ) {
String relayID = intent.getStringExtra( RELAY_ID );
sendNoConnMessage( rowid, relayID, msg );
} else {
receiveMessage( this, rowid, null, msg, s_addr );
}
break;
case INVITE:
startUDPThreadsIfNot();
srcDevID = intent.getIntExtra( DEV_ID_SRC, 0 );
int destDevID = intent.getIntExtra( DEV_ID_DEST, 0 );
String relayID = intent.getStringExtra( RELAY_ID );
String nliData = intent.getStringExtra( NLI_DATA );
sendInvitation( srcDevID, destDevID, relayID, nliData );
break;
case TIMER_FIRED:
if ( !NetStateCache.netAvail( this ) ) {
Log.w( TAG, "not connecting: no network" );
} else if ( startFetchThreadIfNotUDP() ) {
// do nothing
} else if ( registerWithRelayIfNot() ) {
requestMessages();
}
RelayReceiver.setTimer( this );
break;
case STOP:
stopThreads();
stopSelf();
break;
default:
Assert.fail();
}
result = Service.START_STICKY;
}
}
if ( null == result ) {
result = Service.START_STICKY_COMPATIBILITY;
}
NetStateCache.register( this, this );
DbgUtils.assertOnUIThread( false );
Log.d( TAG, "%s.onHandleWork(cmd=%s)", this, cmdFrom( intent ) );
handleCommand( intent );
resetExitTimer();
return result;
}
@Override
@ -461,8 +382,11 @@ public class RelayService extends XWService
long interval_millis = getMaxIntervalSeconds() * 1000;
RelayReceiver.setTimer( this, interval_millis );
}
stopThreads();
if ( null != mThreads ) {
mThreads.unsetService();
}
super.onDestroy();
Log.d( TAG, "%s.onDestroy() DONE", this );
}
// NetStateCache.StateChangedIf interface
@ -471,6 +395,88 @@ public class RelayService extends XWService
startService( this ); // bad name: will *stop* threads too
}
private void handleCommand( Intent intent )
{
MsgCmds cmd = cmdFrom( intent );
if ( null != cmd ) {
// Log.d( TAG, "handleCommand(): cmd=%s", cmd.toString() );
switch( cmd ) {
case PROCESS_GAME_MSGS:
String[] relayIDs = new String[1];
relayIDs[0] = intent.getStringExtra( RELAY_ID );
long[] rowIDs = DBUtils.getRowIDsFor( this, relayIDs[0] );
if ( 0 < rowIDs.length ) {
byte[][][] msgs = expandMsgsArray( intent );
process( msgs, rowIDs, relayIDs );
}
break;
case PROCESS_DEV_MSGS:
byte[][][] msgss = expandMsgsArray( intent );
for ( byte[][] msgs : msgss ) {
for ( byte[] msg : msgs ) {
gotPacket( msg, true, false );
}
}
break;
case UDP_CHANGED:
startThreads();
break;
case RESET:
stopThreads();
startThreads();
break;
case UPGRADE:
UpdateCheckReceiver.checkVersions( this, false );
break;
case GOT_INVITE:
int srcDevID = intent.getIntExtra( INVITE_FROM, 0 );
NetLaunchInfo nli
= NetLaunchInfo.makeFrom( this, intent.getStringExtra(NLI_DATA) );
receiveInvitation( srcDevID, nli );
break;
case SEND:
case RECEIVE:
case SENDNOCONN:
startUDPThreadsOnce();
long rowid = intent.getLongExtra( ROWID, -1 );
byte[] msg = intent.getByteArrayExtra( BINBUFFER );
if ( MsgCmds.SEND == cmd ) {
sendMessage( rowid, msg );
} else if ( MsgCmds.SENDNOCONN == cmd ) {
String relayID = intent.getStringExtra( RELAY_ID );
sendNoConnMessage( rowid, relayID, msg );
} else {
mHelper.receiveMessage( this, rowid, null, msg, s_addr );
}
break;
case INVITE:
startUDPThreadsOnce();
srcDevID = intent.getIntExtra( DEV_ID_SRC, 0 );
int destDevID = intent.getIntExtra( DEV_ID_DEST, 0 );
String relayID = intent.getStringExtra( RELAY_ID );
String nliData = intent.getStringExtra( NLI_DATA );
sendInvitation( srcDevID, destDevID, relayID, nliData );
break;
case TIMER_FIRED:
if ( !NetStateCache.netAvail( this ) ) {
Log.w( TAG, "not connecting: no network" );
} else if ( startFetchThreadIfNotUDP() ) {
// do nothing
} else if ( registerWithRelayIfNot() ) {
requestMessages();
}
RelayReceiver.setTimer( this );
break;
case STOP:
stopThreads();
stopSelf();
break;
default:
Assert.assertFalse( BuildConfig.DEBUG );
}
}
}
private void setupNotifications( String[] relayIDs, BackMoveResult[] bmrs,
ArrayList<Boolean> locals )
{
@ -511,6 +517,7 @@ public class RelayService extends XWService
{
while ( null != m_fetchThread ) {
Log.w( TAG, "2: m_fetchThread NOT NULL; WHAT TO DO???" );
Assert.assertFalse( BuildConfig.DEBUG );
try {
Thread.sleep( 20 );
} catch( java.lang.InterruptedException ie ) {
@ -519,24 +526,38 @@ public class RelayService extends XWService
}
}
private void startUDPThreadsIfNot()
private UDPThreads startUDPThreadsOnce()
{
UDPThreads threads = null;
if ( XWApp.UDP_ENABLED && relayEnabled( this ) ) {
synchronized ( m_UDPThreadsRef ) {
if ( null == m_UDPThreadsRef.get() ) {
UDPThreads threads = new UDPThreads();
m_UDPThreadsRef.set( threads );
synchronized ( sUDPThreadsRef ) {
threads = sUDPThreadsRef.get();
if ( null == threads ) {
threads = new UDPThreads();
sUDPThreadsRef.set( threads );
threads.start();
}
threads.setService( this );
}
} else {
Log.i( TAG, "startUDPThreadsIfNot(): UDP disabled" );
Log.i( TAG, "startUDPThreadsOnce(): UDP disabled" );
}
} // startUDPThreadsIfNot
return threads;
} // startUDPThreadsOnce
private boolean skipNativeSend()
private void stopUDPThreads()
{
boolean skip = m_nativeFailScore > UDP_FAIL_LIMIT || m_skipUPDSet;
synchronized ( sUDPThreadsRef ) {
UDPThreads threads = sUDPThreadsRef.getAndSet( null );
if ( null != threads ) {
threads.stop();
}
}
}
private static boolean skipNativeSend()
{
boolean skip = sNativeFailScore.get() > UDP_FAIL_LIMIT || sSkipUPDSet;
// Log.d( TAG, "skipNativeSend(score=%d)) => %b", m_nativeFailScore, skip );
return skip;
}
@ -548,8 +569,8 @@ public class RelayService extends XWService
private void noteSent( PacketData packet, boolean fromUDP )
{
Log.d( TAG, "Sent (fromUDP=%b) packet: cmd=%s, id=%d",
fromUDP, packet.m_cmd.toString(), packet.m_packetID );
// Log.d( TAG, "Sent (fromUDP=%b) packet: cmd=%s, id=%d",
// fromUDP, packet.m_cmd.toString(), packet.m_packetID );
if ( fromUDP || packet.m_cmd != XWRelayReg.XWPDEV_ACK ) {
List<PacketData> list = fromUDP ? s_packetsSentUDP : s_packetsSentWeb;
synchronized( list ) {
@ -562,25 +583,15 @@ public class RelayService extends XWService
{
long nowMS = System.currentTimeMillis();
List<PacketData> map = fromUDP ? s_packetsSentUDP : s_packetsSentWeb;
Log.d( TAG, "noteSent(fromUDP=%b): adding %d; size before: %d",
fromUDP, packets.size(), map.size() );
// Log.d( TAG, "noteSent(fromUDP=%b): adding %d; size before: %d",
// fromUDP, packets.size(), map.size() );
for ( PacketData packet : packets ) {
if ( fromUDP ) {
packet.setSentMS( nowMS );
}
noteSent( packet, fromUDP );
}
Log.d( TAG, "noteSent(fromUDP=%b): size after: %d", fromUDP, map.size() );
}
private void stopUDPThreadsIf()
{
DbgUtils.assertOnUIThread();
UDPThreads threads = m_UDPThreadsRef.getAndSet( null );
if ( null != threads ) {
threads.stop();
}
// Log.d( TAG, "noteSent(fromUDP=%b): size after: %d", fromUDP, map.size() );
}
// MIGHT BE Running on reader thread
@ -595,14 +606,14 @@ public class RelayService extends XWService
if ( !skipAck ) {
sendAckIf( header );
}
Log.d( TAG, "gotPacket(): cmd=%s", header.m_cmd.toString() );
Log.d( TAG, "%s.gotPacket(): cmd=%s", this, header.m_cmd.toString() );
switch ( header.m_cmd ) {
case XWPDEV_UNAVAIL:
int unavail = dis.readInt();
Log.i( TAG, "relay unvailable for another %d seconds",
unavail );
String str = getVLIString( dis );
postEvent( MultiEvent.RELAY_ALERT, str );
mHelper.postEvent( MultiEvent.RELAY_ALERT, str );
break;
case XWPDEV_ALERT:
str = getVLIString( dis );
@ -647,20 +658,20 @@ public class RelayService extends XWService
break;
case XWPDEV_UPGRADE:
intent = getIntentTo( this, MsgCmds.UPGRADE );
startService( intent );
enqueueWork( this, intent );
break;
case XWPDEV_GOTINVITE:
resetBackoff = true;
intent = getIntentTo( this, MsgCmds.GOT_INVITE );
int srcDevID = dis.readInt();
byte[] nliData = new byte[dis.readShort()];
dis.readFully( nliData );
NetLaunchInfo nli = XwJNI.nliFromStream( nliData );
intent.putExtra( INVITE_FROM, srcDevID );
String asStr = nli.toString();
Log.d( TAG, "got invitation: %s", asStr );
intent.putExtra( NLI_DATA, asStr );
startService( intent );
intent = getIntentTo( this, MsgCmds.GOT_INVITE )
.putExtra( INVITE_FROM, srcDevID )
.putExtra( NLI_DATA, asStr );
enqueueWork( this, intent );
break;
case XWPDEV_ACK:
noteAck( vli2un( dis ), fromUDP );
@ -892,8 +903,7 @@ public class RelayService extends XWService
private void postPacket( ByteArrayOutputStream bas, XWRelayReg cmd )
{
startUDPThreadsIfNot();
UDPThreads threads = m_UDPThreadsRef.get();
UDPThreads threads = startUDPThreadsOnce();
if ( threads != null ) {
threads.add( new PacketData( bas, cmd ) );
}
@ -953,7 +963,7 @@ public class RelayService extends XWService
long rowid = rowIDs[ii];
sink.setRowID( rowid );
for ( byte[] msg : forOne ) {
receiveMessage( this, rowid, sink, msg, s_addr );
mHelper.receiveMessage( this, rowid, sink, msg, s_addr );
}
}
}
@ -961,37 +971,72 @@ public class RelayService extends XWService
}
}
private class UDPThreads {
private static class UDPThreads {
private DatagramSocket m_UDPSocket;
private LinkedBlockingQueue<PacketData> m_queue =
new LinkedBlockingQueue<PacketData>();
private Thread m_UDPReadThread;
private Thread m_UDPWriteThread;
private RelayService[] mServiceHolder = {null};
UDPThreads() {}
void setService( RelayService service )
{
synchronized ( mServiceHolder ) {
mServiceHolder[0] = service;
// unblock waiters for non-null Service
mServiceHolder.notifyAll();
}
}
// It will be a few milliseconds before all threads using the current
// Service instance are done. Blocking the UI thread here until that
// happened make for a laggy UI, so I'm going to see if we can
// continue to use the instance for a short while after returning.
void unsetService()
{
synchronized ( mServiceHolder ) {
mServiceHolder[0] = null;
}
}
private RelayService getService() throws InterruptedException
{
synchronized ( mServiceHolder ) {
while ( null == mServiceHolder[0] ) {
mServiceHolder.wait();
}
return mServiceHolder[0];
}
}
void start()
{
m_UDPReadThread = new Thread( null, new Runnable() {
public void run() {
try {
connectSocket(); // block until this is done
startWriteThread();
connectSocket(); // block until this is done
startWriteThread();
Log.i( TAG, "read thread running" );
byte[] buf = new byte[1024];
for ( ; ; ) {
DatagramPacket packet =
new DatagramPacket( buf, buf.length );
try {
m_UDPSocket.receive( packet );
resetExitTimer();
gotPacket( packet );
} catch ( java.io.InterruptedIOException iioe ) {
// DbgUtils.logf( "FYI: udp receive timeout" );
} catch( java.io.IOException ioe ) {
break;
Log.i( TAG, "read thread running" );
byte[] buf = new byte[1024];
for ( ; ; ) {
DatagramPacket packet =
new DatagramPacket( buf, buf.length );
try {
m_UDPSocket.receive( packet );
final RelayService service = getService();
service.resetExitTimer();
service.gotPacket( packet );
} catch ( java.io.InterruptedIOException iioe ) {
// DbgUtils.logf( "FYI: udp receive timeout" );
} catch( java.io.IOException ioe ) {
break;
}
}
} catch ( InterruptedException ie ) {
Log.d( TAG, "exiting on interrupt: %s",
ie.getMessage() );
}
Log.i( TAG, "read thread exiting" );
}
@ -1001,7 +1046,7 @@ public class RelayService extends XWService
void stop()
{
m_queue.add( new EOQPacketData() ); // will kill the writer thread
m_queue.add( sEOQPacket ); // will kill the writer thread
}
void add( PacketData packet )
@ -1009,11 +1054,15 @@ public class RelayService extends XWService
m_queue.add( packet );
}
private void connectSocket()
private void connectSocket() throws InterruptedException
{
if ( null == m_UDPSocket ) {
int port = XWPrefs.getDefaultRelayPort( RelayService.this );
String host = XWPrefs.getDefaultRelayHost( RelayService.this );
int port;
String host;
final RelayService service = getService();
port = XWPrefs.getDefaultRelayPort( service );
host = XWPrefs.getDefaultRelayHost( service );
try {
m_UDPSocket = new DatagramSocket();
m_UDPSocket.setSoTimeout(30 * 1000); // timeout so we can log
@ -1051,7 +1100,7 @@ public class RelayService extends XWService
for ( outData = m_queue.poll(ts, TimeUnit.SECONDS);
null != outData;
outData = m_queue.poll() ) { // doesn't block
if ( outData instanceof EOQPacketData ) {
if ( outData == sEOQPacket ) {
gotEOQ = true;
break;
} else if ( skipNativeSend() || outData.getForWeb() ) {
@ -1060,18 +1109,19 @@ public class RelayService extends XWService
dataListUDP.add( outData );
}
}
sendViaWeb( dataListWeb );
sendViaUDP( dataListUDP );
getService().resetExitTimer();
runUDPAckTimer();
ConnStatusHandler.showSuccessOut();
} catch ( InterruptedException ie ) {
Log.w( TAG, "write thread killed" );
break;
}
sendViaWeb( dataListWeb );
sendViaUDP( dataListUDP );
resetExitTimer();
runUDPAckTimer();
ConnStatusHandler.showSuccessOut();
}
Log.i( TAG, "write thread killing read thread" );
@ -1090,19 +1140,22 @@ public class RelayService extends XWService
m_UDPWriteThread.start();
}
private int sendViaWeb( List<PacketData> packets )
private int sendViaWeb( List<PacketData> packets ) throws InterruptedException
{
Log.d( TAG, "sendViaWeb(): sending %d at once", packets.size() );
int sentLen = 0;
if ( packets.size() > 0 ) {
HttpURLConnection conn = NetUtils.makeHttpRelayConn( RelayService.this, "post" );
Log.d( TAG, "sendViaWeb(): sending %d at once", packets.size() );
final RelayService service = getService();
HttpURLConnection conn = NetUtils
.makeHttpRelayConn( service, "post" );
if ( null == conn ) {
Log.e( TAG, "sendViaWeb(): null conn for POST" );
} else {
try {
JSONArray dataArray = new JSONArray();
for ( PacketData packet : packets ) {
Assert.assertFalse( packet instanceof EOQPacketData );
Assert.assertFalse( packet == sEOQPacket );
byte[] datum = packet.assemble();
dataArray.put( Utils.base64Encode(datum) );
sentLen += datum.length;
@ -1119,19 +1172,20 @@ public class RelayService extends XWService
int nReplies = resData.length();
// Log.d( TAG, "sendViaWeb(): got %d replies", nReplies );
noteSent( packets, false ); // before we process the acks below :-)
service
.noteSent( packets, false ); // before we process the acks below :-)
for ( int ii = 0; ii < nReplies; ++ii ) {
byte[] datum = Utils.base64Decode( resData.getString( ii ) );
// PENDING: skip ack or not
gotPacket( datum, false, false );
service.gotPacket( datum, false, false );
}
} else {
Log.e( TAG, "sendViaWeb(): failed result for POST" );
}
ConnStatusHandler.updateStatus( RelayService.this, null,
ConnStatusHandler.updateStatus( service, null,
CommsConnType.COMMS_CONN_RELAY,
succeeded );
} catch ( JSONException ex ) {
@ -1142,12 +1196,13 @@ public class RelayService extends XWService
return sentLen;
}
private int sendViaUDP( List<PacketData> packets )
private int sendViaUDP( List<PacketData> packets ) throws InterruptedException
{
int sentLen = 0;
if ( packets.size() > 0 ) {
noteSent( packets, true );
final RelayService service = getService();
service.noteSent( packets, true );
for ( PacketData packet : packets ) {
boolean getOut = true;
byte[] data = packet.assemble();
@ -1161,13 +1216,13 @@ public class RelayService extends XWService
} catch ( java.net.SocketException se ) {
Log.ex( TAG, se );
Log.i( TAG, "Restarting threads to force new socket" );
ConnStatusHandler.updateStatusOut( RelayService.this, null,
ConnStatusHandler.updateStatusOut( service, null,
CommsConnType.COMMS_CONN_RELAY,
true );
m_handler.post( new Runnable() {
service.m_handler.post( new Runnable() {
public void run() {
stopUDPThreadsIf();
service.stopUDPThreads();
}
} );
break;
@ -1181,7 +1236,7 @@ public class RelayService extends XWService
}
}
ConnStatusHandler.updateStatus( RelayService.this, null,
ConnStatusHandler.updateStatus( service, null,
CommsConnType.COMMS_CONN_RELAY,
sentLen > 0 );
}
@ -1216,7 +1271,7 @@ public class RelayService extends XWService
forResend.add( packet );
if ( packet.m_cmd != XWRelayReg.XWPDEV_ACK ) {
foundNonAck = true;
++m_nativeFailScore;
sNativeFailScore.incrementAndGet();
}
iter.remove();
}
@ -1393,13 +1448,13 @@ public class RelayService extends XWService
// Log.d( TAG, "noteAck(fromUDP=%b): removed for id %d: %s",
// fromUDP, packetID, packet );
if ( fromUDP ) {
--m_nativeFailScore;
sNativeFailScore.decrementAndGet();
}
} else {
Log.w( TAG, "Weird: got ack %d but never sent", packetID );
}
if ( BuildConfig.DEBUG ) {
if ( false && BuildConfig.DEBUG ) {
ArrayList<String> pstrs = new ArrayList<>();
for ( PacketData datum : map ) {
if ( 0 != datum.m_packetID ) {
@ -1422,6 +1477,7 @@ public class RelayService extends XWService
// Called from any thread
private void resetExitTimer()
{
// Log.d( TAG, "resetExitTimer()" );
m_handler.removeCallbacks( m_onInactivity );
// UDP socket's no good as a return address after several
@ -1437,10 +1493,11 @@ public class RelayService extends XWService
stopThreads();
} else if ( XWApp.UDP_ENABLED ) {
stopFetchThreadIf();
startUDPThreadsIfNot();
startUDPThreadsOnce();
registerWithRelay();
} else {
stopUDPThreadsIf();
Assert.assertFalse( BuildConfig.DEBUG );
stopUDPThreads();
startFetchThreadIfNotUDP();
}
}
@ -1449,7 +1506,7 @@ public class RelayService extends XWService
{
Log.d( TAG, "stopThreads()" );
stopFetchThreadIf();
stopUDPThreadsIf();
stopUDPThreads();
}
private static void un2vli( int nn, OutputStream os )
@ -1528,7 +1585,8 @@ public class RelayService extends XWService
result = figureBackoffSeconds();
}
Log.d( TAG, "getMaxIntervalSeconds() => %d", result ); // WFT? went from 40 to 1000
// WFT? went from 40 to 1000
// Log.d( TAG, "getMaxIntervalSeconds() => %d", result );
return result;
}
@ -1616,7 +1674,7 @@ public class RelayService extends XWService
}
}
private class PacketData {
private static class PacketData {
public ByteArrayOutputStream m_bas;
public XWRelayReg m_cmd;
public byte[] m_header;
@ -1670,8 +1728,8 @@ public class RelayService extends XWService
try {
m_packetID = nextPacketID( m_cmd );
DataOutputStream out = new DataOutputStream( bas );
Log.d( TAG, "makeHeader(): building packet with cmd %s",
m_cmd.toString() );
// Log.d( TAG, "makeHeader(): building packet with cmd %s",
// m_cmd.toString() );
out.writeByte( XWPDevProto.XWPDEV_PROTO_VERSION_1.ordinal() );
un2vli( m_packetID, out );
out.writeByte( m_cmd.ordinal() );
@ -1682,6 +1740,25 @@ public class RelayService extends XWService
}
}
// Exits only to exist, so instanceof can distinguish
private class EOQPacketData extends PacketData {}
private class RelayServiceHelper extends XWServiceHelper {
private Service mService;
RelayServiceHelper( Service service ) {
super( service );
mService = service;
}
@Override
protected MultiMsgSink getSink( long rowid )
{
return new RelayMsgSink().setRowID( rowid );
}
@Override
void postNotification( String device, int gameID, long rowid )
{
String body = LocUtils.getString( mService, R.string.new_relay_body );
GameUtils.postInvitedNotification( mService, gameID, body, rowid );
}
}
}

View file

@ -36,7 +36,6 @@ import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import junit.framework.Assert;
import org.eehouse.android.xw4.DBUtils.SentInvitesInfo;
import org.eehouse.android.xw4.DlgDelegate.Action;
@ -299,7 +298,7 @@ public class SMSInviteDelegate extends InviteDelegate {
}
});
updateListAdapter( m_phoneRecs.toArray( new PhoneRec[m_phoneRecs.size()] ) );
updateListAdapter( m_phoneRecs );
tryEnable();
}

View file

@ -34,7 +34,6 @@ import android.telephony.PhoneNumberUtils;
import android.telephony.SmsManager;
import android.telephony.TelephonyManager;
import junit.framework.Assert;
import org.eehouse.android.xw4.MultiService.DictFetchOwner;
import org.eehouse.android.xw4.MultiService.MultiEvent;
@ -87,6 +86,7 @@ public class SMSService extends XWService {
private BroadcastReceiver m_sentReceiver;
private BroadcastReceiver m_receiveReceiver;
private OnSharedPreferenceChangeListener m_prefsListener;
private SMSServiceHelper mHelper;
private int m_nReceived = 0;
private static int s_nSent = 0;
@ -175,28 +175,28 @@ public class SMSService extends XWService {
public static void stopService( Context context )
{
Intent intent = getIntentTo( context, SMSAction.STOP_SELF );
context.startService( intent );
startService( context, intent );
}
// NBS case
public static void handleFrom( Context context, byte[] buffer,
String phone )
{
Intent intent = getIntentTo( context, SMSAction.HANDLEDATA );
intent.putExtra( BUFFER, buffer );
intent.putExtra( PHONE, phone );
context.startService( intent );
Intent intent = getIntentTo( context, SMSAction.HANDLEDATA )
.putExtra( BUFFER, buffer )
.putExtra( PHONE, phone );
startService( context, intent );
}
public static void inviteRemote( Context context, String phone,
NetLaunchInfo nli )
{
Intent intent = getIntentTo( context, SMSAction.INVITE );
intent.putExtra( PHONE, phone );
Log.w( TAG, "inviteRemote(%s, '%s')", phone, nli );
byte[] data = nli.asByteArray();
intent.putExtra( GAMEDATA_BA, data );
context.startService( intent );
Intent intent = getIntentTo( context, SMSAction.INVITE )
.putExtra( PHONE, phone )
.putExtra( GAMEDATA_BA, data );
startService( context, intent );
}
public static int sendPacket( Context context, String phone,
@ -204,11 +204,11 @@ public class SMSService extends XWService {
{
int nSent = -1;
if ( XWPrefs.getSMSEnabled( context ) ) {
Intent intent = getIntentTo( context, SMSAction.SEND );
intent.putExtra( PHONE, phone );
intent.putExtra( MultiService.GAMEID, gameID );
intent.putExtra( BINBUFFER, binmsg );
context.startService( intent );
Intent intent = getIntentTo( context, SMSAction.SEND )
.putExtra( PHONE, phone )
.putExtra( MultiService.GAMEID, gameID )
.putExtra( BINBUFFER, binmsg );
startService( context, intent );
nSent = binmsg.length;
} else {
Log.i( TAG, "sendPacket: dropping because SMS disabled" );
@ -218,17 +218,17 @@ public class SMSService extends XWService {
public static void gameDied( Context context, int gameID, String phone )
{
Intent intent = getIntentTo( context, SMSAction.REMOVE );
intent.putExtra( PHONE, phone );
intent.putExtra( MultiService.GAMEID, gameID );
context.startService( intent );
Intent intent = getIntentTo( context, SMSAction.REMOVE )
.putExtra( PHONE, phone )
.putExtra( MultiService.GAMEID, gameID );
startService( context, intent );
}
public static void onGameDictDownload( Context context, Intent intentOld )
{
Intent intent = getIntentTo( context, SMSAction.ADDED_MISSING );
intent.fillIn( intentOld, 0 );
context.startService( intent );
startService( context, intent );
}
public static String fromPublicFmt( String msg )
@ -256,10 +256,16 @@ public class SMSService extends XWService {
return result;
}
private static void startService( Context context, Intent intent )
{
Log.d( TAG, "startService(%s)", intent );
context.startService( intent );
}
private static Intent getIntentTo( Context context, SMSAction cmd )
{
Intent intent = new Intent( context, SMSService.class );
intent.putExtra( CMD_STR, cmd.ordinal() );
Intent intent = new Intent( context, SMSService.class )
.putExtra( CMD_STR, cmd.ordinal() );
return intent;
}
@ -272,15 +278,10 @@ public class SMSService extends XWService {
return s_showToasts;
}
@Override
protected MultiMsgSink getSink( long rowid )
{
return new SMSMsgSink( this );
}
@Override
public void onCreate()
{
mHelper = new SMSServiceHelper( this );
if ( Utils.deviceSupportsSMS( this ) ) {
registerReceivers();
} else {
@ -312,6 +313,7 @@ public class SMSService extends XWService {
@Override
public int onStartCommand( Intent intent, int flags, int startId )
{
// Log.d( TAG, "onStartCommand(%s)", intent );
int result = Service.START_NOT_STICKY;
if ( null != intent ) {
int ordinal = intent.getIntExtra( CMD_STR, -1 );
@ -463,10 +465,10 @@ public class SMSService extends XWService {
}
break;
case DEATH:
postEvent( MultiEvent.MESSAGE_NOGAME, msg.gameID );
mHelper.postEvent( MultiEvent.MESSAGE_NOGAME, msg.gameID );
break;
case ACK_INVITE:
postEvent( MultiEvent.NEWGAME_SUCCESS, msg.gameID );
mHelper.postEvent( MultiEvent.NEWGAME_SUCCESS, msg.gameID );
break;
default:
Log.w( TAG, "unexpected cmd %s", msg.cmd );
@ -483,26 +485,17 @@ public class SMSService extends XWService {
for ( SMSProtoMsg msg : msgs ) {
receive( msg, senderPhone );
}
postEvent( MultiEvent.SMS_RECEIVE_OK );
mHelper.postEvent( MultiEvent.SMS_RECEIVE_OK );
} else {
Log.d( TAG, "receiveBuffer(): bogus or incomplete message from %s",
senderPhone );
}
}
@Override
protected void postNotification( String phone, int gameID, long rowid )
{
String owner = Utils.phoneToContact( this, phone, true );
String body = LocUtils.getString( this, R.string.new_name_body_fmt,
owner );
GameUtils.postInvitedNotification( this, gameID, body, rowid );
}
private void makeForInvite( String phone, NetLaunchInfo nli )
{
if ( nli != null ) {
handleInvitation( nli, phone, DictFetchOwner.OWNER_SMS );
mHelper.handleInvitation( nli, phone, DictFetchOwner.OWNER_SMS );
ackInvite( phone, nli.gameID() );
}
}
@ -548,7 +541,7 @@ public class SMSService extends XWService {
} catch ( NullPointerException npe ) {
Assert.fail(); // shouldn't be trying to do this!!!
} catch ( java.lang.SecurityException se ) {
postEvent( MultiEvent.SMS_SEND_FAILED_NOPERMISSION );
mHelper.postEvent( MultiEvent.SMS_SEND_FAILED_NOPERMISSION );
} catch ( Exception ee ) {
Log.ex( TAG, ee );
}
@ -569,11 +562,12 @@ public class SMSService extends XWService {
private boolean feedMessage( int gameID, byte[] msg, CommsAddrRec addr )
{
ReceiveResult rslt = receiveMessage( this, gameID, null, msg, addr );
if ( ReceiveResult.GAME_GONE == rslt ) {
XWServiceHelper.ReceiveResult rslt = mHelper
.receiveMessage( this, gameID, null, msg, addr );
if ( XWServiceHelper.ReceiveResult.GAME_GONE == rslt ) {
sendDiedPacket( addr.sms_phone, gameID );
}
return rslt == ReceiveResult.OK;
return rslt == XWServiceHelper.ReceiveResult.OK;
}
private void registerReceivers()
@ -584,15 +578,15 @@ public class SMSService extends XWService {
{
switch ( getResultCode() ) {
case Activity.RESULT_OK:
postEvent( MultiEvent.SMS_SEND_OK );
mHelper.postEvent( MultiEvent.SMS_SEND_OK );
break;
case SmsManager.RESULT_ERROR_RADIO_OFF:
postEvent( MultiEvent.SMS_SEND_FAILED_NORADIO );
mHelper.postEvent( MultiEvent.SMS_SEND_FAILED_NORADIO );
break;
case SmsManager.RESULT_ERROR_NO_SERVICE:
default:
Log.w( TAG, "FAILURE!!!" );
postEvent( MultiEvent.SMS_SEND_FAILED );
mHelper.postEvent( MultiEvent.SMS_SEND_FAILED );
break;
}
}
@ -649,4 +643,28 @@ public class SMSService extends XWService {
return sendPacket( addr.sms_phone, gameID, buf );
}
}
private class SMSServiceHelper extends XWServiceHelper {
private Service mService;
SMSServiceHelper( Service service ) {
super( service );
mService = service;
}
@Override
protected MultiMsgSink getSink( long rowid )
{
return new SMSMsgSink( SMSService.this );
}
@Override
protected void postNotification( String phone, int gameID, long rowid )
{
String owner = Utils.phoneToContact( mService, phone, true );
String body = LocUtils.getString( mService, R.string.new_name_body_fmt,
owner );
GameUtils.postInvitedNotification( mService, gameID, body, rowid );
}
}
}

View file

@ -36,7 +36,6 @@ import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.Spinner;
import junit.framework.Assert;
import org.eehouse.android.xw4.DlgDelegate.Action;
import org.eehouse.android.xw4.jni.GameSummary;

View file

@ -31,7 +31,6 @@ import android.widget.Button;
import java.io.Serializable;
import junit.framework.Assert;
import org.eehouse.android.xw4.DlgDelegate.Action;
import org.eehouse.android.xw4.DlgDelegate.DlgClickNotify;

View file

@ -38,7 +38,6 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import junit.framework.Assert;
public class TilePickView extends LinearLayout {
private static final String TAG = TilePickView.class.getSimpleName();

View file

@ -35,7 +35,6 @@ import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import junit.framework.Assert;
import org.eehouse.android.xw4.DlgDelegate.Action;
import org.eehouse.android.xw4.DlgDelegate.HasDlgDelegate;

View file

@ -23,6 +23,7 @@ package org.eehouse.android.xw4;
import android.app.Activity;
import android.app.Dialog;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.ContentResolver;
@ -38,6 +39,8 @@ import android.database.Cursor;
import android.media.Ringtone;
import android.media.RingtoneManager;
import android.net.Uri;
import android.os.Build;
import android.os.Looper;
import android.provider.ContactsContract.PhoneLookup;
import android.support.v4.app.NotificationCompat;
import android.support.v4.content.FileProvider;
@ -71,7 +74,6 @@ import java.util.Iterator;
import java.util.List;
import java.util.Random;
import junit.framework.Assert;
import org.eehouse.android.xw4.Perms23.Perm;
import org.eehouse.android.xw4.jni.CommonPrefs;
@ -254,7 +256,9 @@ public class Utils {
defaults |= Notification.DEFAULT_VIBRATE;
}
Notification notification = new NotificationCompat.Builder( context )
String channelID = Channels.getChannelID( context, Channels.ID.GAME_EVENT );
Notification notification =
new NotificationCompat.Builder( context, channelID )
.setContentIntent( pi )
.setSmallIcon( R.drawable.notify )
//.setTicker(body)
@ -526,6 +530,11 @@ public class Utils {
return result;
}
public static boolean isOnUIThread()
{
return Looper.getMainLooper().equals(Looper.myLooper());
}
public static String base64Encode( byte[] in )
{
return Base64.encodeToString( in, Base64.NO_WRAP );
@ -568,7 +577,7 @@ public class Utils {
public static void testSerialization( Serializable obj )
{
if ( BuildConfig.DEBUG ) {
if ( false && BuildConfig.DEBUG ) {
String as64 = serializableToString64( obj );
Object other = string64ToSerializable( as64 );
Assert.assertTrue( other.equals( obj ) );
@ -610,5 +619,4 @@ public class Utils {
}
}
}
}

View file

@ -27,10 +27,11 @@ import android.os.Bundle;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.TextView;
import java.util.Iterator;
import java.util.Map;
import junit.framework.Assert;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
public class WiDirInviteDelegate extends InviteDelegate
implements WiDirService.DevSetListener {
@ -76,11 +77,17 @@ public class WiDirInviteDelegate extends InviteDelegate
WiDirService.unregisterDevSetListener( this );
}
protected void onBarButtonClicked( int id )
{
// not implemented yet as there's no bar button
Assert.assertFalse( BuildConfig.DEBUG );
}
@Override
protected void onChildAdded( View child, InviterItem data )
{
TwoStringPair pair = (TwoStringPair)data;
((TwoStrsItem)child).setStrings( pair.str2, pair.str1 );
((TwoStrsItem)child).setStrings( pair.str2, pair.getDev() );
}
@Override
@ -88,7 +95,7 @@ public class WiDirInviteDelegate extends InviteDelegate
{
for ( int ii = 0; ii < selected.length; ++ii ) {
TwoStringPair pair = (TwoStringPair)selected[ii];
devs[ii] = pair.str1;
devs[ii] = pair.getDev();
}
}
@ -107,13 +114,13 @@ public class WiDirInviteDelegate extends InviteDelegate
private void rebuildList()
{
int count = m_macsToName.size();
TwoStringPair[] pairs = new TwoStringPair[count];
List<TwoStringPair> pairs = new ArrayList<>();
// String[] names = new String[count];
// String[] addrs = new String[count];
Iterator<String> iter = m_macsToName.keySet().iterator();
for ( int ii = 0; ii < count; ++ii ) {
String mac = iter.next();
pairs[ii] = new TwoStringPair(mac, m_macsToName.get(mac) );
pairs.add( new TwoStringPair(mac, m_macsToName.get(mac) ) );
// addrs[ii] = mac;
// names[ii] = m_macsToName.get(mac);
}

View file

@ -69,7 +69,6 @@ import org.eehouse.android.xw4.jni.CommsAddrRec;
import org.eehouse.android.xw4.jni.XwJNI;
import org.eehouse.android.xw4.loc.LocUtils;
import junit.framework.Assert;
public class WiDirService extends XWService {
private static final String TAG = WiDirService.class.getSimpleName();
@ -125,6 +124,7 @@ public class WiDirService extends XWService {
private static Set<String> s_peersSet;
private P2pMsgSink m_sink;
private WiDirServiceHelper mHelper;
public interface DevSetListener {
void setChanged( Map<String, String> macToName );
@ -134,6 +134,7 @@ public class WiDirService extends XWService {
public void onCreate()
{
m_sink = new P2pMsgSink();
mHelper = new WiDirServiceHelper(this);
}
@Override
@ -214,6 +215,8 @@ public class WiDirService extends XWService {
sHavePermission = false;
} catch ( SecurityException se ) { // perm not in manifest
sHavePermission = false;
} catch ( NullPointerException npe ) { // Seeing this on Oreo emulator
sHavePermission = false;
}
}
@ -750,8 +753,9 @@ public class WiDirService extends XWService {
CommsAddrRec addr = new CommsAddrRec( CommsConnType.COMMS_CONN_P2P )
.setP2PParams( macAddress );
ReceiveResult rslt = receiveMessage( this, gameID, m_sink, data, addr );
if ( ReceiveResult.GAME_GONE == rslt ) {
XWServiceHelper.ReceiveResult rslt = mHelper
.receiveMessage( this, gameID, m_sink, data, addr );
if ( XWServiceHelper.ReceiveResult.GAME_GONE == rslt ) {
sendNoGame( null, macAddress, gameID );
}
}
@ -763,27 +767,15 @@ public class WiDirService extends XWService {
NetLaunchInfo nli = NetLaunchInfo.makeFrom( this, nliData );
String returnMac = intent.getStringExtra( KEY_SRC );
if ( !handleInvitation( nli, returnMac, DictFetchOwner.OWNER_P2P ) ) {
if ( !mHelper.handleInvitation( nli, returnMac, DictFetchOwner.OWNER_P2P ) ) {
Log.d( TAG, "handleInvitation() failed" );
}
}
@Override
void postNotification( String device, int gameID, long rowid )
{
Log.e( TAG, "postNotification() doing nothing" );
}
@Override
MultiMsgSink getSink( long rowid )
{
return m_sink;
}
private void handleGameGone( Intent intent )
{
int gameID = intent.getIntExtra( KEY_GAMEID, 0 );
postEvent( MultiEvent.MESSAGE_NOGAME, gameID );
mHelper.postEvent( MultiEvent.MESSAGE_NOGAME, gameID );
}
private void makeGame( NetLaunchInfo nli, String senderMac )
@ -793,7 +785,7 @@ public class WiDirService extends XWService {
CommsAddrRec addr = nli.makeAddrRec( this );
long rowid = GameUtils.makeNewMultiGame( this, nli,
m_sink,
getUtilCtxt() );
mHelper.getUtilCtxt() );
if ( DBUtils.ROWID_NOTFOUND != rowid ) {
if ( null != nli.gameName && 0 < nli.gameName.length() ) {
DBUtils.setName( this, rowid, nli.gameName );
@ -1205,4 +1197,23 @@ public class WiDirService extends XWService {
private class P2pMsgSink extends MultiMsgSink {
public P2pMsgSink() { super( WiDirService.this ); }
}
private class WiDirServiceHelper extends XWServiceHelper {
WiDirServiceHelper( Service service ) {
super( service );
}
@Override
MultiMsgSink getSink( long rowid )
{
return m_sink;
}
@Override
void postNotification( String device, int gameID, long rowid )
{
Log.e( TAG, "postNotification() doing nothing" );
}
}
}

View file

@ -40,7 +40,6 @@ import android.widget.ListView;
import org.eehouse.android.xw4.DlgDelegate.Action;
import junit.framework.Assert;
public class XWActivity extends FragmentActivity
implements Delegator, DlgDelegate.DlgClickNotify {

View file

@ -21,6 +21,11 @@
package org.eehouse.android.xw4;
import android.app.Application;
import android.arch.lifecycle.Lifecycle;
import android.arch.lifecycle.LifecycleObserver;
import android.arch.lifecycle.LifecycleOwner;
import android.arch.lifecycle.OnLifecycleEvent;
import android.arch.lifecycle.ProcessLifecycleOwner;
import android.content.Context;
import android.graphics.Color;
import android.os.Build;
@ -30,9 +35,10 @@ import org.eehouse.android.xw4.jni.XwJNI;
import java.util.UUID;
import junit.framework.Assert;
public class XWApp extends Application {
import static android.arch.lifecycle.Lifecycle.Event.ON_ANY;
public class XWApp extends Application implements LifecycleObserver {
private static final String TAG = XWApp.class.getSimpleName();
public static final boolean BTSUPPORTED = true;
@ -44,8 +50,6 @@ public class XWApp extends Application {
public static final boolean LOCUTILS_ENABLED = false;
public static final boolean CONTEXT_MENUS_ENABLED = true;
public static final boolean OFFER_DUALPANE = false;
// BT class "COMPUTERS" includes tablets like the Nexus 9
public static final boolean BT_SCAN_COMPUTERS = true;
public static final String SMS_PUBLIC_HEADER = "-XW4";
public static final int MAX_TRAY_TILES = 7; // comtypes.h
@ -65,6 +69,8 @@ public class XWApp extends Application {
Assert.assertTrue( s_context == s_context.getApplicationContext() );
super.onCreate();
ProcessLifecycleOwner.get().getLifecycle().addObserver(this);
// This one line should always get logged even if logging is
// off -- because logging is on by default until logEnable is
// called.
@ -85,12 +91,25 @@ public class XWApp extends Application {
}
UpdateCheckReceiver.restartTimer( this );
BTService.startService( this );
RelayService.startService( this );
GCMIntentService.init( this );
WiDirWrapper.init( this );
}
@OnLifecycleEvent(ON_ANY)
public void onAny( LifecycleOwner source, Lifecycle.Event event )
{
Log.d( TAG, "onAny(%s)", event );
switch( event ) {
case ON_RESUME:
BTService.onAppToForeground( this );
break;
case ON_STOP:
BTService.onAppToBackground( this );
break;
}
}
// This is called on emulator only, but good for ensuring no memory leaks
// by forcing JNI cleanup
@Override

View file

@ -27,7 +27,6 @@ import android.preference.DialogPreference;
import android.util.AttributeSet;
import android.view.View;
import junit.framework.Assert;
import org.eehouse.android.xw4.DlgDelegate.Action;
import org.eehouse.android.xw4.jni.CommsAddrRec.CommsConnType;

View file

@ -30,7 +30,6 @@ import android.view.View;
import java.util.HashMap;
import java.util.Map;
import junit.framework.Assert;
abstract class XWDialogFragment extends DialogFragment {
private static final String TAG = XWDialogFragment.class.getSimpleName();

View file

@ -23,7 +23,6 @@ package org.eehouse.android.xw4;
import android.view.View;
import android.view.ViewGroup;
import junit.framework.Assert;
import java.util.Arrays;
import java.util.HashMap;

View file

@ -36,7 +36,6 @@ import android.widget.ListView;
import java.util.HashSet;
import java.util.Set;
import junit.framework.Assert;
abstract class XWFragment extends Fragment implements Delegator {
private static final String TAG = XWFragment.class.getSimpleName();

View file

@ -29,7 +29,6 @@ import android.text.TextUtils;
import org.json.JSONException;
import org.json.JSONObject;
import junit.framework.Assert;
import org.eehouse.android.xw4.jni.CommsAddrRec.CommsConnType;
import org.eehouse.android.xw4.jni.CommsAddrRec.CommsConnTypeSet;

View file

@ -1,6 +1,6 @@
/* -*- compile-command: "find-and-gradle.sh insXw4Deb"; -*- */
/*
* Copyright 2010 - 2012 by Eric House (xwords@eehouse.org). All
* Copyright 2010 - 2018 by Eric House (xwords@eehouse.org). All
* rights reserved.
*
* This program is free software; you can redistribute it and/or
@ -21,165 +21,15 @@
package org.eehouse.android.xw4;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.IBinder;
import junit.framework.Assert;
import org.eehouse.android.xw4.MultiService.DictFetchOwner;
import org.eehouse.android.xw4.MultiService.MultiEvent;
import org.eehouse.android.xw4.jni.CommsAddrRec;
import org.eehouse.android.xw4.jni.JNIThread;
import org.eehouse.android.xw4.jni.UtilCtxt;
import org.eehouse.android.xw4.jni.UtilCtxtImpl;
import java.util.HashSet;
import java.util.Set;
abstract class XWService extends Service {
class XWService extends Service {
private static final String TAG = XWService.class.getSimpleName();
public static enum ReceiveResult { OK, GAME_GONE, UNCONSUMED };
protected static MultiService s_srcMgr = null;
private static Set<String> s_seen = new HashSet<String>();
private UtilCtxt m_utilCtxt;
@Override
public IBinder onBind( Intent intent )
{
return null;
}
public final static void setListener( MultiService.MultiEventListener li )
{
if ( null == s_srcMgr ) {
// DbgUtils.logf( "XWService.setListener: registering %s", li.getClass().getName() );
s_srcMgr = new MultiService();
}
s_srcMgr.setListener( li );
}
protected void postEvent( MultiEvent event, Object ... args )
{
if ( null != s_srcMgr ) {
s_srcMgr.postEvent( event, args );
} else {
Log.d( TAG, "postEvent(): dropping %s event",
event.toString() );
}
}
// Check that we aren't already processing an invitation with this
// inviteID.
private boolean checkNotDupe( NetLaunchInfo nli )
{
String inviteID = nli.inviteID();
boolean isDupe;
synchronized( s_seen ) {
isDupe = s_seen.contains( inviteID );
if ( !isDupe ) {
s_seen.add( inviteID );
}
}
Log.d( TAG, "checkNotDupe('%s') => %b", inviteID, !isDupe );
return !isDupe;
}
abstract void postNotification( String device, int gameID, long rowid );
// Return true if able to start game only
protected boolean handleInvitation( NetLaunchInfo nli, String device,
DictFetchOwner dfo )
{
boolean success = false;
long[] rowids = DBUtils.getRowIDsFor( this, nli.gameID() );
if ( 0 == rowids.length
|| ( rowids.length < nli.nPlayersT // will break for two-per-device game
&& XWPrefs.getSecondInviteAllowed( this ) ) ) {
if ( nli.isValid() && checkNotDupe( nli ) ) {
if ( DictLangCache.haveDict( this, nli.lang, nli.dict ) ) {
long rowid = GameUtils.makeNewMultiGame( this, nli,
getSink( 0 ),
getUtilCtxt() );
if ( null != nli.gameName && 0 < nli.gameName.length() ) {
DBUtils.setName( this, rowid, nli.gameName );
}
postNotification( device, nli.gameID(), rowid );
success = true;
} else {
Intent intent = MultiService
.makeMissingDictIntent( this, nli, dfo );
MultiService.postMissingDictNotification( this, intent,
nli.gameID() );
}
}
}
Log.d( TAG, "handleInvitation() => %b", success );
return success;
}
protected UtilCtxt getUtilCtxt()
{
if ( null == m_utilCtxt ) {
m_utilCtxt = new UtilCtxtImpl( this );
}
return m_utilCtxt;
}
// Meant to be overridden
abstract MultiMsgSink getSink( long rowid );
protected ReceiveResult receiveMessage( Context context, int gameID,
MultiMsgSink sink, byte[] msg,
CommsAddrRec addr )
{
ReceiveResult result;
long[] rowids = DBUtils.getRowIDsFor( context, gameID );
if ( null == rowids || 0 == rowids.length ) {
result = ReceiveResult.GAME_GONE;
} else {
result = ReceiveResult.UNCONSUMED;
for ( long rowid : rowids ) {
if ( receiveMessage( context, rowid, sink, msg, addr ) ) {
result = ReceiveResult.OK;
}
}
}
return result;
}
protected boolean receiveMessage( Context context, long rowid,
MultiMsgSink sink, byte[] msg,
CommsAddrRec addr )
{
boolean allConsumed = true;
boolean[] isLocalP = new boolean[1];
JNIThread jniThread = JNIThread.getRetained( rowid, false );
boolean consumed = false;
if ( null != jniThread ) {
consumed = true;
jniThread.receive( msg, addr ).release();
} else {
GameUtils.BackMoveResult bmr = new GameUtils.BackMoveResult();
if ( null == sink ) {
sink = getSink( rowid );
}
if ( GameUtils.feedMessage( context, rowid, msg, addr,
sink, bmr, isLocalP ) ) {
consumed = true;
GameUtils.postMoveNotification( context, rowid, bmr,
isLocalP[0] );
}
}
if ( allConsumed && !consumed ) {
allConsumed = false;
}
return allConsumed;
}
}

View file

@ -0,0 +1,215 @@
/* -*- compile-command: "find-and-gradle.sh insXw4Deb"; -*- */
/*
* Copyright 2010 - 2018 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) any later version.
*
* 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.
*/
package org.eehouse.android.xw4;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import org.eehouse.android.xw4.MultiService.DictFetchOwner;
import org.eehouse.android.xw4.MultiService.MultiEvent;
import org.eehouse.android.xw4.jni.CommsAddrRec;
import org.eehouse.android.xw4.jni.CurGameInfo;
import org.eehouse.android.xw4.jni.JNIThread;
import org.eehouse.android.xw4.jni.UtilCtxt;
import org.eehouse.android.xw4.jni.UtilCtxtImpl;
import org.eehouse.android.xw4.jni.XwJNI.GamePtr;
import java.util.HashMap;
import java.util.Map;
abstract class XWServiceHelper {
private static final String TAG = XWServiceHelper.class.getSimpleName();
private Service mService;
private static MultiService s_srcMgr = null;
public static enum ReceiveResult { OK, GAME_GONE, UNCONSUMED };
XWServiceHelper( Service service )
{
mService = service;
}
abstract MultiMsgSink getSink( long rowid );
abstract void postNotification( String device, int gameID, long rowid );
protected ReceiveResult receiveMessage( Context context, int gameID,
MultiMsgSink sink, byte[] msg,
CommsAddrRec addr )
{
ReceiveResult result;
long[] rowids = DBUtils.getRowIDsFor( context, gameID );
if ( null == rowids || 0 == rowids.length ) {
result = ReceiveResult.GAME_GONE;
} else {
result = ReceiveResult.UNCONSUMED;
for ( long rowid : rowids ) {
if ( receiveMessage( context, rowid, sink, msg, addr ) ) {
result = ReceiveResult.OK;
}
}
}
return result;
}
protected boolean receiveMessage( Context context, long rowid,
MultiMsgSink sink, byte[] msg,
CommsAddrRec addr )
{
boolean allConsumed = true;
boolean[] isLocalP = new boolean[1];
JNIThread jniThread = JNIThread.getRetained( context, rowid );
boolean consumed = false;
if ( null != jniThread ) {
consumed = true;
jniThread.receive( msg, addr ).release();
} else {
GameUtils.BackMoveResult bmr = new GameUtils.BackMoveResult();
if ( null == sink ) {
sink = getSink( rowid );
}
if ( GameUtils.feedMessage( context, rowid, msg, addr,
sink, bmr, isLocalP ) ) {
consumed = true;
GameUtils.postMoveNotification( context, rowid, bmr,
isLocalP[0] );
}
}
if ( allConsumed && !consumed ) {
allConsumed = false;
}
return allConsumed;
}
public final static void setListener( MultiService.MultiEventListener li )
{
if ( null == s_srcMgr ) {
// DbgUtils.logf( "XWService.setListener: registering %s",
// li.getClass().getName() );
s_srcMgr = new MultiService();
}
s_srcMgr.setListener( li );
}
protected void postEvent( MultiEvent event, Object ... args )
{
if ( null != s_srcMgr ) {
s_srcMgr.postEvent( event, args );
} else {
Log.d( TAG, "postEvent(): dropping %s event",
event.toString() );
}
}
protected boolean handleInvitation( NetLaunchInfo nli, String device,
DictFetchOwner dfo )
{
boolean success = nli.isValid() && checkNotInFlight( nli );
if ( success ) {
long[] rowids = DBUtils.getRowIDsFor( mService, nli.gameID() );
if ( 0 == rowids.length ) {
// cool: we're good
} else if ( rowids.length < nli.nPlayersT ) {
success = XWPrefs.getSecondInviteAllowed( mService );
// Allowing a second game allows the common testing action of
// sending invitation to myself. But we still need to check
// for duplicates! forceChannel's hard to dig up, but works
for ( int ii = 0; success && ii < rowids.length; ++ii ) {
long rowid = rowids[ii];
CurGameInfo gi = null;
try ( GameLock lock = GameLock.getFor( rowid ).tryLockRO() ) {
// drop invite if can't open game; likely a dupe!
if ( null != lock ) {
gi = new CurGameInfo( mService );
GamePtr gamePtr = GameUtils
.loadMakeGame( mService, gi, lock );
gamePtr.release();
}
}
if ( null == gi ) {
// locked. Maybe it's open?
JNIThread thread = JNIThread.getRetained( mService, rowid );
if ( null != thread ) {
gi = thread.getGI();
thread.release( false );
}
}
success = null != gi && gi.forceChannel != nli.forceChannel;
}
} else {
success = false;
}
if ( success ) {
if ( DictLangCache.haveDict( mService, nli.lang, nli.dict ) ) {
long rowid = GameUtils.makeNewMultiGame( mService, nli,
getSink( 0 ),
getUtilCtxt() );
if ( null != nli.gameName && 0 < nli.gameName.length() ) {
DBUtils.setName( mService, rowid, nli.gameName );
}
postNotification( device, nli.gameID(), rowid );
} else {
Intent intent = MultiService
.makeMissingDictIntent( mService, nli, dfo );
MultiService.postMissingDictNotification( mService, intent,
nli.gameID() );
success = false;
}
}
}
Log.d( TAG, "handleInvitation() => %b", success );
return success;
}
private UtilCtxt m_utilCtxt;
protected UtilCtxt getUtilCtxt()
{
if ( null == m_utilCtxt ) {
m_utilCtxt = new UtilCtxtImpl( mService );
}
return m_utilCtxt;
}
// Check that we aren't already processing an invitation with this
// inviteID.
private static final long SEEN_INTERVAL_MS = 1000 * 2;
private static Map<String, Long> s_seen = new HashMap<>();
private boolean checkNotInFlight( NetLaunchInfo nli )
{
boolean inProcess;
String inviteID = nli.inviteID();
synchronized( s_seen ) {
long now = System.currentTimeMillis();
Long lastSeen = s_seen.get( inviteID );
inProcess = null != lastSeen && lastSeen + SEEN_INTERVAL_MS > now;
if ( !inProcess ) {
s_seen.put( inviteID, now );
}
}
Log.d( TAG, "checkNotInFlight('%s') => %b", inviteID, !inProcess );
return !inProcess;
}
}

View file

@ -23,8 +23,7 @@ package org.eehouse.android.xw4.jni;
import android.content.Context;
import android.text.TextUtils;
import junit.framework.Assert;
import org.eehouse.android.xw4.Assert;
import org.eehouse.android.xw4.BTService;
import org.eehouse.android.xw4.GameUtils;
import org.eehouse.android.xw4.Log;

View file

@ -29,8 +29,8 @@ import java.util.HashSet;
import java.util.Random;
import org.json.JSONObject;
import junit.framework.Assert;
import org.eehouse.android.xw4.Assert;
import org.eehouse.android.xw4.BuildConfig;
import org.eehouse.android.xw4.DictLangCache;
import org.eehouse.android.xw4.DictUtils;

View file

@ -23,8 +23,7 @@ package org.eehouse.android.xw4.jni;
import android.content.Context;
import android.telephony.PhoneNumberUtils;
import junit.framework.Assert;
import org.eehouse.android.xw4.Assert;
import org.eehouse.android.xw4.XWApp;
import org.eehouse.android.xw4.DBUtils;
import org.eehouse.android.xw4.DevID;

View file

@ -28,8 +28,7 @@ import java.util.Arrays;
import org.json.JSONObject;
import junit.framework.Assert;
import org.eehouse.android.xw4.Assert;
import org.eehouse.android.xw4.BuildConfig;
import org.eehouse.android.xw4.DBUtils;
import org.eehouse.android.xw4.Log;

View file

@ -26,8 +26,7 @@ import android.graphics.Bitmap;
import android.os.Handler;
import android.os.Message;
import junit.framework.Assert;
import org.eehouse.android.xw4.Assert;
import org.eehouse.android.xw4.BuildConfig;
import org.eehouse.android.xw4.CommsTransport;
import org.eehouse.android.xw4.ConnStatusHandler;
@ -36,6 +35,7 @@ import org.eehouse.android.xw4.DbgUtils;
import org.eehouse.android.xw4.DictUtils;
import org.eehouse.android.xw4.GameLock;
import org.eehouse.android.xw4.GameUtils;
import org.eehouse.android.xw4.Utils;
import org.eehouse.android.xw4.Log;
import org.eehouse.android.xw4.R;
import org.eehouse.android.xw4.XWPrefs;
@ -217,23 +217,29 @@ public class JNIThread extends Thread {
if ( null != m_jniGamePtr ) {
Log.d( TAG, "configure(): m_jniGamePtr not null; that ok?" );
}
m_jniGamePtr = null;
if ( null != stream ) {
m_jniGamePtr = XwJNI.initFromStream( m_rowid, stream, m_gi,
dictNames, pairs.m_bytes,
pairs.m_paths,
m_gi.langName( m_context ),
utils, null, cp, m_xport );
synchronized ( this ) {
m_jniGamePtr = null;
if ( null != stream ) {
m_jniGamePtr = XwJNI.initFromStream( m_rowid, stream, m_gi,
dictNames, pairs.m_bytes,
pairs.m_paths,
m_gi.langName( m_context ),
utils, null, cp, m_xport );
}
if ( null == m_jniGamePtr ) {
m_jniGamePtr = XwJNI.initNew( m_gi, dictNames, pairs.m_bytes,
pairs.m_paths,
m_gi.langName(m_context),
utils, null, cp, m_xport );
}
Assert.assertNotNull( m_jniGamePtr );
notifyAll();
}
if ( null == m_jniGamePtr ) {
m_jniGamePtr = XwJNI.initNew( m_gi, dictNames, pairs.m_bytes,
pairs.m_paths,
m_gi.langName(m_context),
utils, null, cp, m_xport );
}
Assert.assertNotNull( m_jniGamePtr );
m_lastSavedState = Arrays.hashCode( stream );
}
Log.d( TAG, "configure() => %b", success );
return success;
}
@ -418,13 +424,24 @@ public class JNIThread extends Thread {
}
@SuppressWarnings("fallthrough")
@Override
public void run()
{
Log.d( TAG, "run() starting" );
boolean[] barr = new boolean[2]; // scratch boolean
for ( ; ; ) {
synchronized ( this ) {
if ( m_stopped ) {
break;
} else if ( null == m_jniGamePtr ) {
try {
wait();
} catch ( InterruptedException iex ) {
Log.d( TAG, "exiting run() on interrupt: %s",
iex.getMessage() );
break;
}
continue;
}
}
@ -729,6 +746,7 @@ public class JNIThread extends Thread {
m_jniGamePtr.release();
m_jniGamePtr = null;
}
Log.d( TAG, "run() finished" );
} // run
public void handleBkgrnd( JNICmd cmd, Object... args )
@ -751,8 +769,8 @@ public class JNIThread extends Thread {
public void handle( JNICmd cmd, Object... args )
{
if ( m_stopped && ! JNICmd.CMD_NONE.equals(cmd) ) {
Log.w( TAG, "NOT adding %s to stopped thread!!!", cmd.toString() );
DbgUtils.printStack( TAG );
Log.w( TAG, "handle(%s): NOT adding to stopped thread!!!", cmd );
// DbgUtils.printStack( TAG );
} else {
m_queue.add( new QueueElem( cmd, true, args ) );
}
@ -804,20 +822,27 @@ public class JNIThread extends Thread {
}
}
public static JNIThread getRetained( long rowid )
public static JNIThread getRetained( Context context, long rowid )
{
return getRetained( rowid, false );
return getRetained( context, rowid, false );
}
public static JNIThread getRetained( long rowid, boolean makeNew )
public static JNIThread getRetained( Context context, long rowid, boolean makeNew )
{
JNIThread result = null;
synchronized( s_instances ) {
result = s_instances.get( rowid );
if ( null == result && makeNew ) {
result = new JNIThread( new GameLock( rowid, true ).lock() );
Assert.assertNotNull( result );
s_instances.put( rowid, result );
DbgUtils.assertOnUIThread(); // can't use GameLock.lock()
if ( true /*test done*/ || (0 != Utils.nextRandomInt() % 3) ) {
GameLock lock = GameLock.getFor( rowid ).tryLock();
if ( lock != null ) {
result = new JNIThread( lock );
s_instances.put( rowid, result );
} else {
DbgUtils.toastNoLock( TAG, context, "getRetained(%d)", rowid );
}
}
}
if ( null != result ) {
result.retain_sync();

View file

@ -22,8 +22,7 @@ package org.eehouse.android.xw4.jni;
import android.content.Context;
import junit.framework.Assert;
import org.eehouse.android.xw4.Assert;
import org.eehouse.android.xw4.DBUtils;
import org.eehouse.android.xw4.XWApp;
import org.eehouse.android.xw4.Log;

View file

@ -25,8 +25,7 @@ import android.text.TextUtils;
import java.io.Serializable;
import junit.framework.Assert;
import org.eehouse.android.xw4.Assert;
import org.eehouse.android.xw4.BuildConfig;
import org.eehouse.android.xw4.R;
import org.eehouse.android.xw4.Utils;

View file

@ -67,7 +67,10 @@ public interface UtilCtxt {
void notifyMove( String query );
void notifyTrade( String[] tiles );
// These oughtto be an enum but then I'd have to cons one up in C.
// These can't be an ENUM! The set is open-ended, with arbitrary values
// added to ERR_RELAY_BASE, so no way to create an enum from an int in the
// jni world. int has to be passed into userError(). Trust me: I
// tried. :-)
static final int ERR_NONE = 0;
static final int ERR_TILES_NOT_IN_LINE = 1;
static final int ERR_NO_EMPTIES_IN_TURN = 2;
@ -82,11 +85,12 @@ public interface UtilCtxt {
static final int ERR_REG_SERVER_SANS_REMOTE = 11;
static final int STR_NEED_BT_HOST_ADDR = 12;
static final int ERR_NO_EMPTY_TRADE = 13;
static final int ERR_CANT_UNDO_TILEASSIGN = 14;
static final int ERR_CANT_HINT_WHILE_DISABLED = 15;
static final int ERR_NO_HINT_FOUND = 16;
static final int ERR_TOO_MANY_TRADE = 14;
static final int ERR_CANT_UNDO_TILEASSIGN = 15;
static final int ERR_CANT_HINT_WHILE_DISABLED = 16;
static final int ERR_NO_HINT_FOUND = 17;
static final int ERR_RELAY_BASE = 17;
static final int ERR_RELAY_BASE = 18;
void userError( int id );
void informMove( int turn, String expl, String words );

View file

@ -22,7 +22,6 @@ package org.eehouse.android.xw4.jni;
import android.content.Context;
import junit.framework.Assert;
import org.eehouse.android.xw4.Log;
import org.eehouse.android.xw4.jni.CommsAddrRec.CommsConnTypeSet;

View file

@ -24,8 +24,7 @@ import android.graphics.Rect;
import java.util.Arrays;
import junit.framework.Assert;
import org.eehouse.android.xw4.Assert;
import org.eehouse.android.xw4.Log;
import org.eehouse.android.xw4.NetLaunchInfo;
import org.eehouse.android.xw4.Utils;
@ -59,6 +58,8 @@ public class XwJNI {
return this;
}
public long getRowid() { return m_rowid; }
// Force (via an assert in finalize() below) that this is called. It's
// better if jni stuff isn't being done on the finalizer thread
public synchronized void release()
@ -365,6 +366,7 @@ public class XwJNI {
public static native void server_handleUndo( GamePtr gamePtr );
public static native boolean server_do( GamePtr gamePtr );
public static native void server_tilesPicked( GamePtr gamePtr, int player, int[] tiles );
public static native int server_countTilesInPool( GamePtr gamePtr );
public static native String server_formatDictCounts( GamePtr gamePtr, int nCols );
public static native boolean server_getGameIsOver( GamePtr gamePtr );

View file

@ -42,8 +42,8 @@ import android.widget.Spinner;
import android.widget.SpinnerAdapter;
import android.widget.TextView;
import junit.framework.Assert;
import org.eehouse.android.xw4.Assert;
import org.eehouse.android.xw4.DBUtils;
import org.eehouse.android.xw4.DbgUtils;
import org.eehouse.android.xw4.Log;

Binary file not shown.

After

Width:  |  Height:  |  Size: 308 B

View file

@ -5,8 +5,8 @@
android:layout_width="fill_parent"
android:layout_height="wrap_content"
>
<Button android:id="@+id/button_add"
android:text="@string/bt_pick_addall_button"
<Button android:id="@+id/button_scan"
android:text="@string/button_scan"
style="@style/evenly_spaced_horizontal"
/>

View file

@ -25,6 +25,9 @@
<item android:id="@+id/games_game_copy"
android:title="@string/list_item_copy"
/>
<item android:id="@+id/games_game_netstats"
android:title="@string/list_item_netstats"
/>
<item android:id="@+id/games_game_select"
android:title="@string/list_item_select"
/>

View file

@ -92,8 +92,8 @@
<string name="key_udp_interval">key_udp_interval</string>
<string name="key_notagain_sync">key_notagain_sync</string>
<!-- <string name="key_notagain_sms_ready">key_notagain_sms_ready</string> -->
<string name="key_notagain_newselect">key_notagain_newselect</string>
<string name="key_notagain_emptybtscan">key_notagain_emptybtscan</string>
<string name="key_notagain_backclears">key_notagain_backclears</string>
<string name="key_notagain_chat">key_notagain_chat</string>
<string name="key_notagain_relay">key_notagain_relay</string>
@ -136,7 +136,6 @@
<string name="key_invite_multi">key_invite_multi</string>
<string name="key_na_rematch_two_only">key_notagain_rematch_two_only</string>
<string name="key_notagain_dfltname">key_notagain_dfltname</string>
<string name="key_na_comms_bt">key_na_comms_bt</string>
<string name="key_na_comms_p2p">key_na_comms_p2p</string>
<string name="key_na_comms_sms">key_na_comms_sms</string>

View file

@ -148,6 +148,9 @@
<!-- Title of dialog for renaming game (triggered by selecting
list_item_rename) -->
<!-- long-tap game list menuitem for showing info about game's connectivity -->
<string name="list_item_netstats">Connections…</string>
<!-- If you try to copy a networked game you get this error
message. -->
<string name="no_copy_network">Games that have already connected
@ -1319,6 +1322,14 @@
<string name="str_too_few_tiles_left_to_trade">Too few tiles left
to exchange.</string>
<!-- In Spanish you can trade with fewer than 7 tiles in the
pool, so you can wind up selecting more than are
available. You get this error message in that case. -->
<plurals name="too_many_trade_fmt">
<item quantity="one">Too many tiles selected. Please select only one.</item>
<item quantity="other">Too many tiles selected. Please select no more than %1$d.</item>
</plurals>
<!-- Displayed if you try to use the undo menuitem or button and
there are no tiles on the board (no move has yet been made.)
[If I'm being clever and disabling those features in this
@ -1813,15 +1824,18 @@
<!-- <string name="newgame_enable_bt">Turn Bluetooth on</string> -->
<!-- In the Bluetooth invite device dialog -->
<string name="bt_pick_addall_button">Add all Paired</string>
<!-- -->
<string name="bt_pick_clear_button">Remove checked</string>
<!-- -->
<!-- In invitation dialogs, button to remove checked items -->
<string name="bt_pick_clear_button">Remove</string>
<!-- In the Bluetooth invite device dialog, to launch Andorid BT
settings pair new device -->
<string name="bt_pair_settings">Pair More</string>
<!-- In the Bluetooth invite device dialog, kick off a new scan of
nearby devices -->
<string name="button_scan">Rescan</string>
<!-- -->
<string name="invite_progress_title">Connecting...</string>
<string name="invite_progress_title">Connecting</string>
<string name="invite_progress_fmt">Sending invitation to CrossWords on %1$s</string>
<!-- -->
<string name="summary_wait_host">Waiting for connection[s]</string>
@ -1837,7 +1851,6 @@
<string name="new_bt_body_fmt">A player on the device %1$s wants to start a game</string>
<string name="new_relay_body">Tap to open the new game</string>
<!-- -->
<string name="bt_bad_proto_fmt">The version of CrossWords on
\"%1$s\" is incompatible with this one for play using
@ -1850,13 +1863,20 @@
continue.</string>
<!-- -->
<plurals name="invite_bt_desc_fmt">
<item quantity="one">Please check the device
you want to include in this game.\n\nUse the \"%2$s\"
button if you don\'t see the device you expect.</item>
<item quantity="other">Please check up to %1$d device[s]
you want to include in this game.\n\nUse the \"%2$s\"
button if you don\'t see a device you expect.</item>
<plurals name="invite_bt_desc_fmt_2">
<item quantity="one">Please check the device you want to include
in this game.</item>
<item quantity="other">Please check up to %1$d device[s] you
want to include in this game.</item>
</plurals>
<!-- Appended to above -->
<string name="invite_bt_desc_postscript">\n\n(The list is of devices you\'ve
paired and on which CrossWords has been detected.)</string>
<plurals name="bt_scan_progress_fmt">
<item quantity="one">Scanning for CrossWords</item>
<item quantity="other">Scanning for CrossWords on %1$d paired devices.</item>
</plurals>
<!-- -->
<string name="bt_resend_fmt">Bluetooth send to %1$s failed; retry
@ -2026,6 +2046,10 @@
<string name="title_enable_p2p">Enable WiFi Direct</string>
<string name="summary_enable_p2p">Experimental, uses lots of battery</string>
<string name="bkng_notify_text">Accepting Bluetooth messages…</string>
<string name="bkng_stop_text">Stop</string>
<string name="bkng_settings_text">Settings</string>
<!-- -->
<string name="confirm_sms_title">Confirm your SMS plan</string>
<!-- -->
@ -2393,6 +2417,9 @@
within Bluetooth range and that CrossWords is installed on
it.</string>
<!-- How long ago did we last see a BT device via scan -->
<string name="bt_scan_age_fmt">Last scan response: %1$s</string>
<!-- label within default wordlists in app preferences -->
<string name="default_language">Default language</string>
@ -2447,7 +2474,7 @@
<string name="waiting_invite_title">Waiting for response</string>
<string name="waiting_rematch_title">Rematch in progress</string>
<!-- Button for alert with title above -->
<string name="button_wait">Wait</string>
<string name="button_close">Close</string>
<string name="button_reinvite">Re-invite</string>
<string name="invite_stays">(This dialog will stay up until all
@ -2641,6 +2668,12 @@
player name \"%1$s\". Would you like to personalize with your own
name before you create this game?</string>
<string name="btservice_expl">This notification is present whenever
CrossWords is running in the background to accept Bluetooth
messages. It usually runs for about 15 minutes after CrossWords
starts or a Bluetooth message is received.
</string>
<string name="no_invites">This game has sent no invitations</string>
<string name="disable_dualpane">Disable side-by-side</string>
@ -2715,4 +2748,14 @@
<string name="perms_rationale_title">Android Permissions</string>
<string name="toast_no_permission">Permission not granted</string>
<!-- Explanation in settings for always-on BT notification -->
<string name="foreground_channel_expl">Accepting Bluetooth in background</string>
<!-- Explanation in settings for traditional move-arrived notification -->
<string name="gameevent_channel_expl">In-game events</string>
<string name="not_again_emptybtscan">If a scan doesn\'t find the device you expect:\n
• First, just try again\n
• Make sure Bluetooth is on on the other device\n
• Launch CrossWords on the other device\n
</string>
</resources>

View file

@ -29,7 +29,6 @@ import com.google.android.gcm.GCMRegistrar;
import org.json.JSONArray;
import junit.framework.Assert;
public class GCMIntentService extends GCMBaseIntentService {
private static final String TAG = GCMIntentService.class.getSimpleName();

Binary file not shown.

After

Width:  |  Height:  |  Size: 471 B

View file

@ -2,9 +2,9 @@
buildscript {
repositories {
google()
jcenter()
maven { url 'https://maven.fabric.io/public' }
google()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.1.2'
@ -17,6 +17,7 @@ buildscript {
allprojects {
repositories {
google()
jcenter()
}
}
@ -29,7 +30,7 @@ subprojects {
afterEvaluate {project ->
if (project.hasProperty("android")) {
android {
compileSdkVersion 23
compileSdkVersion 26
}
}
}

View file

@ -103,6 +103,15 @@ and_util_getMD5SumForDict( JNIUtilCtxt* jniutil, const XP_UCHAR* name,
jstring result =
(*env)->CallObjectMethod( env, jniutil->jjniutil, mid, jname, jbytes );
deleteLocalRefs( env, jname, jbytes, DELETE_NO_REF );
#ifdef DEBUG
if ( !!result ) {
const char* chars = (*env)->GetStringUTFChars( env, result, NULL );
XP_LOGF( "%s(%s, len=%d) => %s", __func__, name, len, chars );
(*env)->ReleaseStringUTFChars( env, result, chars );
}
#endif
return result;
}

View file

@ -702,10 +702,10 @@ and_dutil_deviceRegistered( XW_DUtilCtxt* duc, DevIDType typ,
const XP_UCHAR* idRelay )
{
DUTIL_CBK_HEADER( "deviceRegistered",
"(L" PKG_PATH("jni/UtilCtxt$DevIDType") ";Ljava/lang/String;)V" );
"(L" PKG_PATH("jni/DUtilCtxt$DevIDType") ";Ljava/lang/String;)V" );
jstring jstr = (*env)->NewStringUTF( env, idRelay );
jobject jtyp = intToJEnum( env, typ,
PKG_PATH("jni/UtilCtxt$DevIDType") );
PKG_PATH("jni/DUtilCtxt$DevIDType") );
(*env)->CallVoidMethod( env, dutil->jdutil, mid, jtyp, jstr );
deleteLocalRefs( env, jstr, jtyp, DELETE_NO_REF );
DUTIL_CBK_TAIL();

Some files were not shown because too many files have changed in this diff Show more