Merge remote-tracking branch 'origin/android_branch' into android_translate

This commit is contained in:
Eric House 2016-01-07 07:30:15 -08:00
commit 5c14ad3f22
199 changed files with 6510 additions and 1769 deletions

View file

@ -22,7 +22,7 @@
to come from a domain that you own or have control over. --> to come from a domain that you own or have control over. -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.eehouse.android.xw4dbg" package="org.eehouse.android.xw4dbg"
android:versionCode="91" android:versionCode="95"
android:versionName="@string/app_version" android:versionName="@string/app_version"
> >
@ -70,6 +70,7 @@
<application android:icon="@drawable/icon48x48" <application android:icon="@drawable/icon48x48"
android:label="@string/app_name" android:label="@string/app_name"
android:name=".XWApp" android:name=".XWApp"
android:debuggable="true"
> >
<activity android:name="GamesListActivity" <activity android:name="GamesListActivity"
@ -106,6 +107,11 @@
android:configChanges="keyboardHidden|orientation|screenSize" android:configChanges="keyboardHidden|orientation|screenSize"
android:screenOrientation="sensor" android:screenOrientation="sensor"
/> />
<activity android:name="RelayInviteActivity"
android:label="@string/relay_invite_title"
android:theme="@android:style/Theme.Dialog"
android:configChanges="keyboardHidden|orientation|screenSize"
/>
<activity android:name="GameConfigActivity" <activity android:name="GameConfigActivity"
android:screenOrientation="sensor" android:screenOrientation="sensor"

View file

@ -2,3 +2,4 @@ BasEnglish2to8.xwd
CollegeEng_2to8.xwd CollegeEng_2to8.xwd
Top5000.xwd Top5000.xwd
changes.html changes.html
gitvers.txt

View file

@ -1,6 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project name="XWords4" default="release"> <project name="XWords4" default="release">
<!-- eeh: allow ant files to refer e.g. to ${env.HOME} -->
<property environment="env" />
<!-- The local.properties file is created and updated by the 'android' tool. <!-- The local.properties file is created and updated by the 'android' tool.
It contains the path to the SDK. It should *NOT* be checked into It contains the path to the SDK. It should *NOT* be checked into
Version Control Systems. --> Version Control Systems. -->
@ -52,7 +55,7 @@
<!-- extension targets. Uncomment the ones where you want to do custom work <!-- extension targets. Uncomment the ones where you want to do custom work
in between standard targets --> in between standard targets -->
<property name="INITIAL_CLIENT_VERS" value="4"/> <property name="INITIAL_CLIENT_VERS" value="8"/>
<property name="VARIANT_NAME" value="xw4dbg"/> <property name="VARIANT_NAME" value="xw4dbg"/>
<property name="APP_NAME" value="CrossDbg"/> <property name="APP_NAME" value="CrossDbg"/>

View file

@ -41,5 +41,6 @@ conn_types_display.xml
expander_header.xml expander_header.xml
msg_label_and_edit.xml msg_label_and_edit.xml
not_again_view.xml not_again_view.xml
relayinviter.xml
remote_dict_details.xml remote_dict_details.xml
toolbar.xml toolbar.xml

View file

@ -1,10 +1,11 @@
board_menu.xml board_menu.xml
chat_menu.xml chat_menu.xml
dicts_item_menu.xml dicts_item_menu.xml
games_list_item_menu.xml
games_list_menu.xml games_list_menu.xml
dicts_menu.xml dicts_menu.xml
studylist.xml studylist.xml
loc_menu.xml loc_menu.xml
empty.xml empty.xml
loc_item_menu.xml loc_item_menu.xml
games_list_group_menu.xml
games_list_game_menu.xml

View file

@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_version">4.4-dbg beta 80</string>
</resources>

View file

@ -1 +1,2 @@
/strings.xml /strings.xml
strings.xml

View file

@ -0,0 +1 @@
strings.xml

View file

@ -1 +1,2 @@
/strings.xml /strings.xml
strings.xml

View file

@ -0,0 +1 @@
strings.xml

View file

@ -0,0 +1 @@
strings.xml

View file

@ -0,0 +1 @@
strings.xml

View file

@ -0,0 +1 @@
strings.xml

View file

@ -0,0 +1 @@
strings.xml

View file

@ -1,4 +1,3 @@
#CrashTrack.java#
/DlgID.java /DlgID.java
/LookupAlert.java /LookupAlert.java
ABUtils.java ABUtils.java
@ -113,11 +112,16 @@ GroupStateListener.java
ListGroup.java ListGroup.java
ConnViaViewLayout.java ConnViaViewLayout.java
Delegator.java Delegator.java
DevID.java
HeaderWithExpander.java HeaderWithExpander.java
LangListPreference.java LangListPreference.java
ListDelegator.java ListDelegator.java
NagTurnReceiver.java NagTurnReceiver.java
NotAgainView.java NotAgainView.java
OnBootReceiver.java OnBootReceiver.java
RelayInviteActivity.java
RelayInviteDelegate.java
XWConnAddrPreference.java XWConnAddrPreference.java
XWDevIDPreference.java
XWExpListAdapter.java XWExpListAdapter.java
RequestCode.java

View file

@ -1,9 +1,19 @@
*.apk *.apk
.DS_Store
.gradle
/.idea/libraries
/.idea/workspace.xml
/build
/captures
/libs-debug
/libs-release
/local.properties
/obj-debug
/obj-release
ant_out.txt ant_out.txt
local.properties
bin bin
gen gen
libs local.properties
proguard.cfg
obj obj
proguard.cfg
res/drawable*/*gen.png res/drawable*/*gen.png

View file

@ -0,0 +1 @@
XWords4

View file

@ -0,0 +1,231 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectCodeStyleSettingsManager">
<option name="PER_PROJECT_SETTINGS">
<value>
<option name="CLASS_COUNT_TO_USE_IMPORT_ON_DEMAND" value="99" />
<option name="NAMES_COUNT_TO_USE_IMPORT_ON_DEMAND" value="99" />
<option name="PACKAGES_TO_USE_IMPORT_ON_DEMAND">
<value />
</option>
<option name="IMPORT_LAYOUT_TABLE">
<value>
<package name="android" withSubpackages="true" static="false" />
<emptyLine />
<package name="com" withSubpackages="true" static="false" />
<emptyLine />
<package name="junit" withSubpackages="true" static="false" />
<emptyLine />
<package name="net" withSubpackages="true" static="false" />
<emptyLine />
<package name="org" withSubpackages="true" static="false" />
<emptyLine />
<package name="java" withSubpackages="true" static="false" />
<emptyLine />
<package name="javax" withSubpackages="true" static="false" />
<emptyLine />
<package name="" withSubpackages="true" static="false" />
<emptyLine />
<package name="" withSubpackages="true" static="true" />
<emptyLine />
</value>
</option>
<option name="RIGHT_MARGIN" value="100" />
<AndroidXmlCodeStyleSettings>
<option name="USE_CUSTOM_SETTINGS" value="true" />
</AndroidXmlCodeStyleSettings>
<Objective-C-extensions>
<option name="GENERATE_INSTANCE_VARIABLES_FOR_PROPERTIES" value="ASK" />
<option name="RELEASE_STYLE" value="IVAR" />
<option name="TYPE_QUALIFIERS_PLACEMENT" value="BEFORE" />
<file>
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Import" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Macro" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Typedef" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Enum" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Constant" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Global" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Struct" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="FunctionPredecl" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Function" />
</file>
<class>
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Property" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Synthesize" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="InitMethod" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="StaticMethod" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="InstanceMethod" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="DeallocMethod" />
</class>
<extensions>
<pair source="cpp" header="h" />
<pair source="c" header="h" />
</extensions>
</Objective-C-extensions>
<XML>
<option name="XML_KEEP_LINE_BREAKS" value="false" />
<option name="XML_ALIGN_ATTRIBUTES" value="false" />
<option name="XML_SPACE_INSIDE_EMPTY_TAG" value="true" />
</XML>
<codeStyleSettings language="XML">
<option name="FORCE_REARRANGE_MODE" value="1" />
<indentOptions>
<option name="CONTINUATION_INDENT_SIZE" value="4" />
</indentOptions>
<arrangement>
<rules>
<section>
<rule>
<match>
<AND>
<NAME>xmlns:android</NAME>
<XML_NAMESPACE />
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>xmlns:.*</NAME>
<XML_NAMESPACE />
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*:id</NAME>
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*:name</NAME>
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>name</NAME>
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>style</NAME>
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*:layout_width</NAME>
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*:layout_height</NAME>
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*:layout_.*</NAME>
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*:width</NAME>
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*:height</NAME>
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_NAMESPACE>.*</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
</rules>
</arrangement>
</codeStyleSettings>
</value>
</option>
<option name="PREFERRED_PROJECT_CODE_STYLE" value="Default (1)" />
</component>
</project>

View file

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<resourceExtensions />
<wildcardResourcePatterns>
<entry name="!?*.java" />
<entry name="!?*.form" />
<entry name="!?*.class" />
<entry name="!?*.groovy" />
<entry name="!?*.scala" />
<entry name="!?*.flex" />
<entry name="!?*.kt" />
<entry name="!?*.clj" />
<entry name="!?*.aj" />
</wildcardResourcePatterns>
<annotationProcessing>
<profile default="true" name="Default" enabled="false">
<processorPath useClasspath="true" />
</profile>
</annotationProcessing>
</component>
</project>

View file

@ -0,0 +1,3 @@
<component name="CopyrightManager">
<settings default="" />
</component>

View file

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
<option name="distributionType" value="DEFAULT_WRAPPED" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="gradleJvm" value="1.7" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />
<option value="$PROJECT_DIR$/app" />
</set>
</option>
</GradleProjectSettings>
</option>
</component>
</project>

View file

@ -0,0 +1,62 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="EntryPointsManager">
<entry_points version="2.0" />
</component>
<component name="NullableNotNullManager">
<option name="myDefaultNullable" value="android.support.annotation.Nullable" />
<option name="myDefaultNotNull" value="android.support.annotation.NonNull" />
<option name="myNullables">
<value>
<list size="4">
<item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.Nullable" />
<item index="1" class="java.lang.String" itemvalue="javax.annotation.Nullable" />
<item index="2" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.Nullable" />
<item index="3" class="java.lang.String" itemvalue="android.support.annotation.Nullable" />
</list>
</value>
</option>
<option name="myNotNulls">
<value>
<list size="4">
<item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.NotNull" />
<item index="1" class="java.lang.String" itemvalue="javax.annotation.Nonnull" />
<item index="2" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.NonNull" />
<item index="3" class="java.lang.String" itemvalue="android.support.annotation.NonNull" />
</list>
</value>
</option>
</component>
<component name="ProjectLevelVcsManager" settingsEditedManually="false">
<OptionsSetting value="true" id="Add" />
<OptionsSetting value="true" id="Remove" />
<OptionsSetting value="true" id="Checkout" />
<OptionsSetting value="true" id="Update" />
<OptionsSetting value="true" id="Status" />
<OptionsSetting value="true" id="Edit" />
<ConfirmationsSetting value="0" id="Add" />
<ConfirmationsSetting value="0" id="Remove" />
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_6" default="true" assert-keyword="true" jdk-15="true" project-jdk-name="1.7" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">
<option name="id" value="Android" />
</component>
<component name="masterDetails">
<states>
<state key="ProjectJDKs.UI">
<settings>
<last-edited>1.7</last-edited>
<splitter-proportions>
<option name="proportions">
<list>
<option value="0.2" />
</list>
</option>
</splitter-proportions>
</settings>
</state>
</states>
</component>
</project>

View file

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/XWords4.iml" filepath="$PROJECT_DIR$/XWords4.iml" />
<module fileurl="file://$PROJECT_DIR$/app/app.iml" filepath="$PROJECT_DIR$/app/app.iml" />
</modules>
</component>
</project>

View file

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RunConfigurationProducerService">
<option name="ignoredProducers">
<set>
<option value="org.jetbrains.plugins.gradle.execution.test.runner.AllInPackageGradleConfigurationProducer" />
<option value="org.jetbrains.plugins.gradle.execution.test.runner.TestClassGradleConfigurationProducer" />
<option value="org.jetbrains.plugins.gradle.execution.test.runner.TestMethodGradleConfigurationProducer" />
</set>
</option>
</component>
</project>

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$/../../.." vcs="Git" />
</component>
</project>

View file

@ -22,7 +22,7 @@
to come from a domain that you own or have control over. --> to come from a domain that you own or have control over. -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.eehouse.android.xw4" package="org.eehouse.android.xw4"
android:versionCode="91" android:versionCode="95"
android:versionName="@string/app_version" android:versionName="@string/app_version"
> >
@ -37,6 +37,7 @@
android:largeScreens="true" android:largeScreens="true"
android:xlargeScreens="true" android:xlargeScreens="true"
/> />
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
@ -102,6 +103,11 @@
android:configChanges="keyboardHidden|orientation|screenSize" android:configChanges="keyboardHidden|orientation|screenSize"
android:screenOrientation="sensor" android:screenOrientation="sensor"
/> />
<activity android:name="RelayInviteActivity"
android:label="@string/relay_invite_title"
android:theme="@android:style/Theme.Dialog"
android:configChanges="keyboardHidden|orientation|screenSize"
/>
<activity android:name="GameConfigActivity" <activity android:name="GameConfigActivity"
android:screenOrientation="sensor" android:screenOrientation="sensor"

View file

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<module external.linked.project.id="XWords4" external.linked.project.path="$MODULE_DIR$" external.root.project.path="$MODULE_DIR$" external.system.id="GRADLE" external.system.module.group="" external.system.module.version="unspecified" type="JAVA_MODULE" version="4">
<component name="FacetManager">
<facet type="java-gradle" name="Java-Gradle">
<configuration>
<option name="BUILD_FOLDER_PATH" value="$MODULE_DIR$/build" />
<option name="BUILDABLE" value="false" />
</configuration>
</facet>
</component>
<component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_6" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/.gradle" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

View file

@ -0,0 +1 @@
/build

View file

@ -0,0 +1,93 @@
<?xml version="1.0" encoding="UTF-8"?>
<module external.linked.project.id=":app" external.linked.project.path="$MODULE_DIR$" external.root.project.path="$MODULE_DIR$/.." external.system.id="GRADLE" external.system.module.group="XWords4" external.system.module.version="unspecified" type="JAVA_MODULE" version="4">
<component name="FacetManager">
<facet type="android-gradle" name="Android-Gradle">
<configuration>
<option name="GRADLE_PROJECT_PATH" value=":app" />
</configuration>
</facet>
<facet type="android" name="Android">
<configuration>
<option name="SELECTED_BUILD_VARIANT" value="debug" />
<option name="SELECTED_TEST_ARTIFACT" value="_android_test_" />
<option name="ASSEMBLE_TASK_NAME" value="assembleDebug" />
<option name="COMPILE_JAVA_TASK_NAME" value="compileDebugSources" />
<option name="ASSEMBLE_TEST_TASK_NAME" value="assembleDebugAndroidTest" />
<option name="COMPILE_JAVA_TEST_TASK_NAME" value="compileDebugAndroidTestSources" />
<afterSyncTasks>
<task>generateDebugAndroidTestSources</task>
<task>generateDebugSources</task>
</afterSyncTasks>
<option name="ALLOW_USER_CONFIGURATION" value="false" />
<option name="MANIFEST_FILE_RELATIVE_PATH" value="/src/main/AndroidManifest.xml" />
<option name="RES_FOLDER_RELATIVE_PATH" value="/src/main/res" />
<option name="RES_FOLDERS_RELATIVE_PATH" value="file://$MODULE_DIR$/src/main/res" />
<option name="ASSETS_FOLDER_RELATIVE_PATH" value="/src/main/assets" />
</configuration>
</facet>
</component>
<component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_6" inherit-compiler-output="false">
<output url="file://$MODULE_DIR$/build/intermediates/classes/debug" />
<output-test url="file://$MODULE_DIR$/build/intermediates/classes/androidTest/debug" />
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/r/debug" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/aidl/debug" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/buildConfig/debug" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/rs/debug" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/rs/debug" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/resValues/debug" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/r/androidTest/debug" isTestSource="true" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/aidl/androidTest/debug" isTestSource="true" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/buildConfig/androidTest/debug" isTestSource="true" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/rs/androidTest/debug" isTestSource="true" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/rs/androidTest/debug" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/resValues/androidTest/debug" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/res" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/resources" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/assets" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/aidl" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/java" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/jni" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/rs" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/res" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/main/resources" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/main/assets" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/main/aidl" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/jni" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/rs" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/res" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/resources" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/assets" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/aidl" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/java" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/jni" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/rs" isTestSource="true" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/assets" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/classes" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/classes-proguard" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/debug" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/dependency-cache" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/dex" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/incremental" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/lint" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/manifests" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/mockable-android-14.jar" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/pre-dexed" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/proguard-rules" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/release" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/res" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/rs" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/symbols" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/tmp" />
<excludeFolder url="file://$MODULE_DIR$/build/outputs" />
<excludeFolder url="file://$MODULE_DIR$/build/reports" />
<excludeFolder url="file://$MODULE_DIR$/build/test-results" />
<excludeFolder url="file://$MODULE_DIR$/build/tmp" />
</content>
<orderEntry type="jdk" jdkName="Android API 14 Platform" jdkType="Android SDK" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" exported="" name="gcm" level="project" />
</component>
</module>

View file

@ -0,0 +1,209 @@
def VARIANT_NAME = 'xw4'
def INITIAL_CLIENT_VERS = 6
def CHAT_ENABLED = true
def THUMBNAIL_ENABLED = true
def LIBS_DEBUG = 'libs-debug'
def LIBS_RELEASE = 'libs-release'
apply plugin: 'com.android.application'
dependencies {
compile files('../libs/gcm.jar')
}
android {
compileSdkVersion 22
buildToolsVersion "22.0.1"
defaultConfig {
applicationId "org.eehouse.android.xw4"
minSdkVersion 7
targetSdkVersion 14
}
// Rename all output artifacts to include version information
applicationVariants.all { variant ->
renameArtifact(variant)
}
// flavorDimensions "variant", "abi"
// productFlavors {
// xw4 {
// dimension "variant"
// }
// xw4dbg {
// dimension "variant"
// }
// armeabiv7a {
// dimension "abi"
// }
// armeabi {
// dimension "abi"
// }
// x86 {
// dimension "abi"
// }
// }
signingConfigs {
release {
storeFile file(System.getenv("HOME") + "/.keystore")
keyAlias "mykey"
// These two lines make gradle believe that the signingConfigs
// section is complete. Without them, tasks like installRelease
// will not be available!
storePassword "notReal"
keyPassword "notReal"
}
}
buildTypes {
release {
signingConfig signingConfigs.release
debuggable false
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
debug {
debuggable true
// This doesn't work on marshmallow: duplicate permission error
// applicationIdSuffix ".debug"
}
}
sourceSets {
// Use symlinks instead of setting non-conventional
// directories here. AS doesn't respect what's set here: it'll
// compile, but post-install app launch and source-level
// debugging don't work.
release {
jniLibs.srcDir "../$LIBS_RELEASE"
}
debug {
jniLibs.srcDir "../$LIBS_DEBUG"
}
}
lintOptions {
abortOnError false
}
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
}
task genVers(type: Exec) {
workingDir '../'
commandLine '../scripts/genvers.sh', '--variant', VARIANT_NAME,
'--client-vers', INITIAL_CLIENT_VERS, '--chat-enabled', CHAT_ENABLED,
'--thumbnail-enabled', THUMBNAIL_ENABLED,
'--vers-outfile', "assets/gitvers.txt"
}
task mkImages(type: Exec) {
workingDir '../'
commandLine '../scripts/mkimages.sh'
}
task copyStrings(type: Exec) {
workingDir '../'
commandLine '../scripts/copy-strings.py'
}
task ndkSetup(type: Exec) {
workingDir '../'
commandLine "../scripts/ndksetup.sh"
}
task genGcmid(type: Exec) {
workingDir '../'
commandLine '../scripts/gen_gcmid.sh',
'-o', "src/org/eehouse/android/$VARIANT_NAME/GCMConsts.java",
'-v', "$VARIANT_NAME"
}
task myPreBuild(dependsOn: ['genVers', 'ndkSetup', 'mkImages', 'copyStrings', 'mkXml', 'genGcmid']) {
}
preBuild.dependsOn myPreBuild
task ndkBuildDebug(type: Exec) {
workingDir '../'
commandLine '../scripts/ndkbuild.sh', '-j3', "CHAT_ENABLED=$CHAT_ENABLED",
"THUMBNAIL_ENABLED=$THUMBNAIL_ENABLED", 'BUILD_TARGET=debug',
"INITIAL_CLIENT_VERS=$INITIAL_CLIENT_VERS", "VARIANT=$VARIANT_NAME",
"NDK_LIBS_OUT=$LIBS_DEBUG", 'NDK_OUT=./obj-debug'
}
task ndkBuildRelease(type: Exec) {
workingDir '../'
commandLine '../scripts/ndkbuild.sh', '-j3', "CHAT_ENABLED=$CHAT_ENABLED",
"THUMBNAIL_ENABLED=$THUMBNAIL_ENABLED", 'BUILD_TARGET=release',
"INITIAL_CLIENT_VERS=$INITIAL_CLIENT_VERS", "VARIANT=$VARIANT_NAME",
"NDK_LIBS_OUT=$LIBS_RELEASE", 'NDK_OUT=./obj-release'
}
task mkXml(type: Exec) {
workingDir '../'
commandLine '../scripts/mk_xml.py', '-o',
"src/org/eehouse/android/$VARIANT_NAME/loc/LocIDsData.java",
'-t', "debug", '-v', "$VARIANT_NAME"
}
afterEvaluate {
compileReleaseNdk.dependsOn ndkBuildRelease
compileDebugNdk.dependsOn ndkBuildDebug
}
task askForPassword << {
def password = System.getenv("ANDROID_KEY_PASS")
if (null == password || 0 == password.length()) {
if ( null != System.console() ) {
password = new String(System.console()
.readPassword("ANDROID_KEY_PASS not set; "
+ "Keystore password: "))
} else {
password = null
println( "ANDROID_KEY_PASS not set and no console; " )
println( "sign it yerself later. (Or you might try" )
println( " running gradlew with the --no-daemon flag)" )
}
}
if ( null != password ) {
android.signingConfigs.release.storePassword = password
android.signingConfigs.release.keyPassword = password
}
}
tasks.whenTaskAdded { theTask ->
if (theTask.name.equals("packageRelease")) {
theTask.dependsOn "askForPassword"
}
}
def getVersionName() {
try {
def stdout = new ByteArrayOutputStream()
exec {
commandLine 'git', 'describe', '--dirty'
standardOutput = stdout
}
return stdout.toString().trim()
}
catch (ignored) {
return null;
}
}
def renameArtifact(variant) {
variant.outputs.each { output ->
def name = String.format( "XWords4-%s-%s.apk", variant.name,
getVersionName() )
output.outputFile = new File( (String)output.outputFile.parent,
(String)name )
}
}

View file

@ -0,0 +1,34 @@
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in /home/eehouse/android/android-sdk-linux/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Keep everything. Useful maybe for confirming that proguard's causing
# a problem?
# -keep class ** { *; }
# Don't turn this on until it's clear that proguard isn't messing
# anything else up. It'll make crash reports useless.
-dontobfuscate
# Uncomment this if obfuscation is enabled (by removing the line
# above), and save the mapping.txt file or confirm it can be rebuilt
# from a tag.
# -keepattributes SourceFile,LineNumberTable
# Prevents crash when jni code calls setInt on various jin.* classes
-keep public class org.eehouse.android.xw4.jni.** { public *; }

View file

@ -0,0 +1 @@
../../../AndroidManifest.xml

View file

@ -0,0 +1 @@
../../../assets/

View file

@ -0,0 +1 @@
../../../src/

View file

@ -0,0 +1 @@
../../../res

View file

@ -0,0 +1 @@
/gitvers.txt

View file

@ -13,10 +13,10 @@
</style> </style>
</head> </head>
<body> <body>
<h2>Crosswords 4.4 beta 98 release</h2> <h2>Crosswords 4.4 beta 101 release</h2>
<p>This release is mostly to get the new Dutch translation out <p>This is the second of two releases that together fix stalling
there, and to catch up Catalan and French.</p> issues in network games.</p>
<div id="survey"> <div id="survey">
<p>Please <a href="https://www.surveymonkey.com/s/GX3XLHR">take <p>Please <a href="https://www.surveymonkey.com/s/GX3XLHR">take
@ -26,23 +26,8 @@
<h3>New with this release</h3> <h3>New with this release</h3>
<ul> <ul>
<li>Complete and up-to-date translations into Dutch, French and <li>Fix to send correctly calculated identifier for game
Catalan. What language is next?</li> state.</li>
<li>Allow copying invitation URL to clipboard so you can paste
it into any messaging app you like</li>
<li>Improvements to in-game chat experience (but more are coming)</li>
<li>Show a "toast" when hint button can't find any moves</li>
<li>Add SMS to the list of ways you can invite somebody</li>
<li>When displaying Bluetooth-connected devices to invite, skip
stuff like headphones</li>
<li>Turn off email invite attachments. They didn't seem to work anyway,
and aren't needed on modern Android.</li>
<li>Fix very old memory leak</li>
</ul> </ul>
<p>(The full changelog <p>(The full changelog
@ -51,8 +36,9 @@
<h3>Next up</h3> <h3>Next up</h3>
<ul> <ul>
<li>Offer &quot;Rematch&quot; when game&apos;s over (Easy via <li>Offer &quot;Rematch&quot; when game&apos;s over (Easy via
SMS and Bluetooth; harder via the internet/relay)</li> SMS and Bluetooth; harder via the internet/relay)</li>
<li>Look into supporting play via peer-to-peer wifi</li> <li>Take advantage of Marshmallow's new permissions model (where
the app only asks for them when it needs them.)
</ul> </ul>
<p>Please let me know <p>Please let me know

View file

@ -0,0 +1,34 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:1.3.0'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
jcenter()
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
subprojects {
afterEvaluate {project ->
if (project.hasProperty("android")) {
android {
compileSdkVersion 14
buildToolsVersion '22.0.1'
}
}
}
}

View file

@ -1,6 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project name="XWords4" default="release"> <project name="XWords4" default="release">
<!-- eeh: allow ant files to refer e.g. to ${env.HOME} -->
<property environment="env" />
<!-- The local.properties file is created and updated by the 'android' tool. <!-- The local.properties file is created and updated by the 'android' tool.
It contains the path to the SDK. It should *NOT* be checked into It contains the path to the SDK. It should *NOT* be checked into
Version Control Systems. --> Version Control Systems. -->
@ -52,7 +55,7 @@
<!-- extension targets. Uncomment the ones where you want to do custom work <!-- extension targets. Uncomment the ones where you want to do custom work
in between standard targets --> in between standard targets -->
<property name="INITIAL_CLIENT_VERS" value="6"/> <property name="INITIAL_CLIENT_VERS" value="8"/>
<property name="VARIANT_NAME" value="xw4"/> <property name="VARIANT_NAME" value="xw4"/>
<property name="APP_NAME" value="Crosswords"/> <property name="APP_NAME" value="Crosswords"/>

View file

@ -0,0 +1,18 @@
# Project-wide Gradle settings.
# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
# Default value: -Xmx10248m -XX:MaxPermSize=256m
# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true

Binary file not shown.

View file

@ -0,0 +1,6 @@
#Mon Nov 09 19:22:17 PST 2015
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-2.4-all.zip

164
xwords4/android/XWords4/gradlew vendored Executable file
View file

@ -0,0 +1,164 @@
#!/usr/bin/env bash
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn ( ) {
echo "$*"
}
die ( ) {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
esac
# For Cygwin, ensure paths are in UNIX format before anything is touched.
if $cygwin ; then
[ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
fi
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >&-
APP_HOME="`pwd -P`"
cd "$SAVED" >&-
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
function splitJvmOpts() {
JVM_OPTS=("$@")
}
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"

90
xwords4/android/XWords4/gradlew.bat vendored Normal file
View file

@ -0,0 +1,90 @@
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windowz variants
if not "%OS%" == "Windows_NT" goto win9xME_args
if "%@eval[2+2]" == "4" goto 4NT_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
goto execute
:4NT_args
@rem Get arguments from the 4NT Shell from JP Software
set CMD_LINE_ARGS=%$
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

View file

@ -39,6 +39,7 @@ LOCAL_DEFINES += \
-DXWFEATURE_BASE64 \ -DXWFEATURE_BASE64 \
-DXWFEATURE_DEVID \ -DXWFEATURE_DEVID \
-DCOMMON_LAYOUT \ -DCOMMON_LAYOUT \
-DNATIVE_NLI \
-DCOMMS_VERSION=1 \ -DCOMMS_VERSION=1 \
-DINITIAL_CLIENT_VERS=${INITIAL_CLIENT_VERS} \ -DINITIAL_CLIENT_VERS=${INITIAL_CLIENT_VERS} \
-DVARIANT_${VARIANT} \ -DVARIANT_${VARIANT} \
@ -84,7 +85,7 @@ COMMON_SRC_FILES += \
$(COMMON_PATH)/memstream.c \ $(COMMON_PATH)/memstream.c \
$(COMMON_PATH)/movestak.c \ $(COMMON_PATH)/movestak.c \
$(COMMON_PATH)/dbgutil.c \ $(COMMON_PATH)/dbgutil.c \
$(COMMON_PATH)/nli.c \
LOCAL_CFLAGS+=$(LOCAL_C_INCLUDES) $(LOCAL_DEFINES) -Wall -std=c99 LOCAL_CFLAGS+=$(LOCAL_C_INCLUDES) $(LOCAL_DEFINES) -Wall -std=c99
LOCAL_SRC_FILES := $(linux_SRC_FILES) $(LOCAL_SRC_FILES) $(COMMON_SRC_FILES) LOCAL_SRC_FILES := $(linux_SRC_FILES) $(LOCAL_SRC_FILES) $(COMMON_SRC_FILES)

View file

@ -1,6 +1,6 @@
/* -*- compile-command: "cd ..; ../scripts/ndkbuild.sh -j3"; -*- */ /* -*- compile-command: "find-and-ant.sh debug install"; -*- */
/* /*
* Copyright © 2009 - 2011 by Eric House (xwords@eehouse.org). All rights * Copyright © 2009 - 2016 by Eric House (xwords@eehouse.org). All rights
* reserved. * reserved.
* *
* This program is free software; you can redistribute it and/or * This program is free software; you can redistribute it and/or
@ -648,7 +648,7 @@ makeDict( MPFORMAL JNIEnv *env, DictMgrCtxt* dictMgr, JNIUtilCtxt* jniutil, jstr
anddict->super.langName = getStringCopy( MPPARM(mpool) anddict->super.langName = getStringCopy( MPPARM(mpool)
env, jlangname ); env, jlangname );
XP_U32 numEdges; XP_U32 numEdges = 0;
XP_Bool parses = parseDict( anddict, (XP_U8*)anddict->bytes, XP_Bool parses = parseDict( anddict, (XP_U8*)anddict->bytes,
bytesSize, &numEdges ); bytesSize, &numEdges );
if ( !parses || (check && !checkSanity( &anddict->super, if ( !parses || (check && !checkSanity( &anddict->super,

View file

@ -78,12 +78,14 @@ error error error
int int
getInt( JNIEnv* env, jobject obj, const char* name ) getInt( JNIEnv* env, jobject obj, const char* name )
{ {
// XP_LOGF( "%s(name=%s)", __func__, name );
jclass cls = (*env)->GetObjectClass( env, obj ); jclass cls = (*env)->GetObjectClass( env, obj );
XP_ASSERT( !!cls ); XP_ASSERT( !!cls );
jfieldID fid = (*env)->GetFieldID( env, cls, name, "I"); jfieldID fid = (*env)->GetFieldID( env, cls, name, "I");
XP_ASSERT( !!fid ); XP_ASSERT( !!fid );
int result = (*env)->GetIntField( env, obj, fid ); int result = (*env)->GetIntField( env, obj, fid );
deleteLocalRef( env, cls ); deleteLocalRef( env, cls );
// XP_LOGF( "%s(name=%s) => %d", __func__, name, result );
return result; return result;
} }
@ -113,12 +115,14 @@ getInts( JNIEnv* env, void* cobj, jobject jobj, const SetInfo* sis, XP_U16 nSis
void void
setInt( JNIEnv* env, jobject obj, const char* name, int value ) setInt( JNIEnv* env, jobject obj, const char* name, int value )
{ {
// XP_LOGF( "%s(name=%s)", __func__, name );
jclass cls = (*env)->GetObjectClass( env, obj ); jclass cls = (*env)->GetObjectClass( env, obj );
XP_ASSERT( !!cls ); XP_ASSERT( !!cls );
jfieldID fid = (*env)->GetFieldID( env, cls, name, "I"); jfieldID fid = (*env)->GetFieldID( env, cls, name, "I");
XP_ASSERT( !!fid ); XP_ASSERT( !!fid );
(*env)->SetIntField( env, obj, fid, value ); (*env)->SetIntField( env, obj, fid, value );
deleteLocalRef( env, cls ); deleteLocalRef( env, cls );
// XP_LOGF( "%s(name=%s) DONE", __func__, name );
} }
void void
@ -178,6 +182,7 @@ setBools( JNIEnv* env, jobject jobj, void* cobj, const SetInfo* sis, XP_U16 nSis
bool bool
setString( JNIEnv* env, jobject obj, const char* name, const XP_UCHAR* value ) setString( JNIEnv* env, jobject obj, const char* name, const XP_UCHAR* value )
{ {
// XP_LOGF( "%s(%s)", __func__, name );
bool success = false; bool success = false;
jclass cls = (*env)->GetObjectClass( env, obj ); jclass cls = (*env)->GetObjectClass( env, obj );
jfieldID fid = (*env)->GetFieldID( env, cls, name, "Ljava/lang/String;" ); jfieldID fid = (*env)->GetFieldID( env, cls, name, "Ljava/lang/String;" );
@ -193,10 +198,32 @@ setString( JNIEnv* env, jobject obj, const char* name, const XP_UCHAR* value )
return success; return success;
} }
void
getStrings( JNIEnv* env, void* cobj, jobject jobj, const SetInfo* sis, XP_U16 nSis )
{
for ( int ii = 0; ii < nSis; ++ii ) {
const SetInfo* si = &sis[ii];
XP_UCHAR* buf = (XP_UCHAR*)(((uint8_t*)cobj) + si->offset);
getString( env, jobj, si->name, buf, si->siz );
}
}
void
setStrings( JNIEnv* env, jobject jobj, void* cobj, const SetInfo* sis, XP_U16 nSis )
{
for ( int ii = 0; ii < nSis; ++ii ) {
const SetInfo* si = &sis[ii];
// XP_LOGF( "calling setString(%s)", si->name );
XP_UCHAR* val = (XP_UCHAR*)(((uint8_t*)cobj) + si->offset);
setString( env, jobj, si->name, val );
}
}
void void
getString( JNIEnv* env, jobject obj, const char* name, XP_UCHAR* buf, getString( JNIEnv* env, jobject obj, const char* name, XP_UCHAR* buf,
int bufLen ) int bufLen )
{ {
// XP_LOGF( "%s(%s)", __func__, name );
jclass cls = (*env)->GetObjectClass( env, obj ); jclass cls = (*env)->GetObjectClass( env, obj );
XP_ASSERT( !!cls ); XP_ASSERT( !!cls );
jfieldID fid = (*env)->GetFieldID( env, cls, name, "Ljava/lang/String;" ); jfieldID fid = (*env)->GetFieldID( env, cls, name, "Ljava/lang/String;" );
@ -214,6 +241,7 @@ getString( JNIEnv* env, jobject obj, const char* name, XP_UCHAR* buf,
buf[len] = '\0'; buf[len] = '\0';
deleteLocalRef( env, cls ); deleteLocalRef( env, cls );
// XP_LOGF( "%s(%s) DONE", __func__, name );
} }
XP_UCHAR* XP_UCHAR*
@ -403,9 +431,15 @@ getMethodID( JNIEnv* env, jobject obj, const char* proc, const char* sig )
XP_ASSERT( !!env ); XP_ASSERT( !!env );
jclass cls = (*env)->GetObjectClass( env, obj ); jclass cls = (*env)->GetObjectClass( env, obj );
XP_ASSERT( !!cls ); XP_ASSERT( !!cls );
#ifdef DEBUG
char buf[128] = {0};
/* int len = sizeof(buf); */
/* getClassName( env, obj, buf, &len ); */
#endif
jmethodID mid = (*env)->GetMethodID( env, cls, proc, sig ); jmethodID mid = (*env)->GetMethodID( env, cls, proc, sig );
if ( !mid ) { if ( !mid ) {
XP_LOGF( "%s: no mid for proc %s, sig %s", __func__, proc, sig ); XP_LOGF( "%s: no mid for proc %s, sig %s in object of class %s",
__func__, proc, sig, buf );
} }
XP_ASSERT( !!mid ); XP_ASSERT( !!mid );
deleteLocalRef( env, cls ); deleteLocalRef( env, cls );
@ -703,8 +737,46 @@ android_debugf( const char* format, ... )
# endif # endif
, buf ); , buf );
} }
#endif
/* Print an object's class name into buffer.
*
* NOTE: this must be called in advance of any jni error, because methods on
* env can't be called once there's an exception pending.
*/
#if 0
static void
getClassName( JNIEnv* env, jobject obj, char* out, int* outLen )
{
XP_ASSERT( !!obj );
jclass cls1 = (*env)->GetObjectClass( env, obj );
// First get the class object
jmethodID mid = (*env)->GetMethodID( env, cls1, "getClass",
"()Ljava/lang/Class;" );
jobject clsObj = (*env)->CallObjectMethod( env, obj, mid );
// Now get the class object's class descriptor
jclass cls2 = (*env)->GetObjectClass( env, clsObj );
// Find the getName() method on the class object
mid = (*env)->GetMethodID( env, cls2, "getName", "()Ljava/lang/String;" );
// Call the getName() to get a jstring object back
jstring strObj = (jstring)(*env)->CallObjectMethod( env, clsObj, mid );
jint slen = (*env)->GetStringUTFLength( env, strObj );
if ( slen < *outLen ) {
*outLen = slen;
(*env)->GetStringUTFRegion( env, strObj, 0, slen, out );
out[slen] = '\0';
} else {
*outLen = 0;
out[0] = '\0';
}
deleteLocalRefs( env, clsObj, cls1, cls2, strObj, DELETE_NO_REF );
LOG_RETURNF( "%s", out );
}
#endif
#endif
/* #ifdef DEBUG */ /* #ifdef DEBUG */
/* XP_U32 */ /* XP_U32 */

View file

@ -58,6 +58,10 @@ void setBools( JNIEnv* env, jobject jobj, void* cobj,
bool setString( JNIEnv* env, jobject obj, const char* name, const XP_UCHAR* value ); bool setString( JNIEnv* env, jobject obj, const char* name, const XP_UCHAR* value );
void getString( JNIEnv* env, jobject jlp, const char* name, XP_UCHAR* buf, void getString( JNIEnv* env, jobject jlp, const char* name, XP_UCHAR* buf,
int bufLen ); int bufLen );
void getStrings( JNIEnv* env, void* cobj, jobject jobj,
const SetInfo* sis, XP_U16 nSis );
void setStrings( JNIEnv* env, jobject jobj, void* cobj,
const SetInfo* sis, XP_U16 nSis );
XP_UCHAR* getStringCopy( MPFORMAL JNIEnv* env, jstring jname ); XP_UCHAR* getStringCopy( MPFORMAL JNIEnv* env, jstring jname );
void setObject( JNIEnv* env, jobject obj, const char* name, const char* sig, void setObject( JNIEnv* env, jobject obj, const char* name, const char* sig,
jobject val ); jobject val );

View file

@ -66,15 +66,16 @@ and_xport_getFlags( void* closure )
} }
static XP_S16 static XP_S16
and_xport_send( const XP_U8* buf, XP_U16 len, const CommsAddrRec* addr, and_xport_send( const XP_U8* buf, XP_U16 len, const XP_UCHAR* msgNo,
CommsConnType conType, XP_U32 gameID, void* closure ) const CommsAddrRec* addr, CommsConnType conType,
XP_U32 gameID, void* closure )
{ {
jint result = -1; jint result = -1;
LOG_FUNC(); LOG_FUNC();
AndTransportProcs* aprocs = (AndTransportProcs*)closure; AndTransportProcs* aprocs = (AndTransportProcs*)closure;
if ( NULL != aprocs->jxport ) { if ( NULL != aprocs->jxport ) {
JNIEnv* env = ENVFORME( aprocs->ti ); JNIEnv* env = ENVFORME( aprocs->ti );
const char* sig = "([BL" PKG_PATH("jni/CommsAddrRec") const char* sig = "([BLjava/lang/String;L" PKG_PATH("jni/CommsAddrRec")
";L" PKG_PATH("jni/CommsAddrRec$CommsConnType") ";I)I"; ";L" PKG_PATH("jni/CommsAddrRec$CommsConnType") ";I)I";
jmethodID mid = getMethodID( env, aprocs->jxport, "transportSend", sig ); jmethodID mid = getMethodID( env, aprocs->jxport, "transportSend", sig );
@ -83,15 +84,15 @@ and_xport_send( const XP_U8* buf, XP_U16 len, const CommsAddrRec* addr,
jobject jaddr = makeJAddr( env, addr ); jobject jaddr = makeJAddr( env, addr );
jobject jConType = jobject jConType =
intToJEnum(env, conType, PKG_PATH("jni/CommsAddrRec$CommsConnType")); intToJEnum(env, conType, PKG_PATH("jni/CommsAddrRec$CommsConnType"));
jstring jMsgNo = !!msgNo ? (*env)->NewStringUTF( env, msgNo ) : NULL;
result = (*env)->CallIntMethod( env, aprocs->jxport, mid, result = (*env)->CallIntMethod( env, aprocs->jxport, mid,
jbytes, jaddr, jConType, gameID ); jbytes, jMsgNo, jaddr, jConType, gameID );
deleteLocalRefs( env, jaddr, jbytes, jConType, DELETE_NO_REF ); deleteLocalRefs( env, jaddr, jbytes, jMsgNo, jConType, DELETE_NO_REF );
} }
LOG_RETURNF( "%d", result ); LOG_RETURNF( "%d", result );
return result; return result;
} }
static void static void
and_xport_relayStatus( void* closure, CommsRelayState newState ) and_xport_relayStatus( void* closure, CommsRelayState newState )
{ {
@ -126,7 +127,7 @@ and_xport_relayConnd( void* closure, XP_UCHAR* const room, XP_Bool reconnect,
} }
static XP_Bool static XP_Bool
and_xport_sendNoConn( const XP_U8* buf, XP_U16 len, and_xport_sendNoConn( const XP_U8* buf, XP_U16 len, const XP_UCHAR* msgNo,
const XP_UCHAR* relayID, void* closure ) const XP_UCHAR* relayID, void* closure )
{ {
jboolean result = false; jboolean result = false;
@ -134,14 +135,15 @@ and_xport_sendNoConn( const XP_U8* buf, XP_U16 len,
if ( NULL != aprocs && NULL != aprocs->jxport ) { if ( NULL != aprocs && NULL != aprocs->jxport ) {
JNIEnv* env = ENVFORME( aprocs->ti ); JNIEnv* env = ENVFORME( aprocs->ti );
const char* sig = "([BLjava/lang/String;)Z"; const char* sig = "([BLjava/lang/String;Ljava/lang/String;)Z";
jmethodID mid = getMethodID( env, aprocs->jxport, jmethodID mid = getMethodID( env, aprocs->jxport,
"relayNoConnProc", sig ); "relayNoConnProc", sig );
jbyteArray jbytes = makeByteArray( env, len, (jbyte*)buf ); jbyteArray jbytes = makeByteArray( env, len, (jbyte*)buf );
jstring jRelayID = (*env)->NewStringUTF( env, relayID ); jstring jRelayID = (*env)->NewStringUTF( env, relayID );
jstring jMsgNo = !!msgNo ? (*env)->NewStringUTF( env, msgNo ) : NULL;
result = (*env)->CallBooleanMethod( env, aprocs->jxport, mid, result = (*env)->CallBooleanMethod( env, aprocs->jxport, mid,
jbytes, jRelayID ); jbytes, jMsgNo, jRelayID );
deleteLocalRefs( env, jbytes, jRelayID, DELETE_NO_REF ); deleteLocalRefs( env, jbytes, jRelayID, jMsgNo, DELETE_NO_REF );
} }
LOG_RETURNF( "%d", result ); LOG_RETURNF( "%d", result );
return result; return result;

View file

@ -32,6 +32,7 @@
#include "dictnry.h" #include "dictnry.h"
#include "dictiter.h" #include "dictiter.h"
#include "dictmgr.h" #include "dictmgr.h"
#include "nli.h"
#include "utilwrapper.h" #include "utilwrapper.h"
#include "drawwrapper.h" #include "drawwrapper.h"
@ -283,6 +284,46 @@ makeGI( MPFORMAL JNIEnv* env, jobject jgi )
return gi; return gi;
} /* makeGI */ } /* makeGI */
static const SetInfo nli_ints[] = {
ARR_MEMBER( NetLaunchInfo, _conTypes ),
ARR_MEMBER( NetLaunchInfo, lang ),
ARR_MEMBER( NetLaunchInfo, forceChannel ),
ARR_MEMBER( NetLaunchInfo, nPlayersT ),
ARR_MEMBER( NetLaunchInfo, nPlayersH ),
ARR_MEMBER( NetLaunchInfo, gameID ),
ARR_MEMBER( NetLaunchInfo, osVers ),
};
static const SetInfo nli_bools[] = {
ARR_MEMBER( NetLaunchInfo, isGSM )
};
static const SetInfo nli_strs[] = {
ARR_MEMBER( NetLaunchInfo, dict ),
ARR_MEMBER( NetLaunchInfo, gameName ),
ARR_MEMBER( NetLaunchInfo, room ),
ARR_MEMBER( NetLaunchInfo, btName ),
ARR_MEMBER( NetLaunchInfo, btAddress ),
ARR_MEMBER( NetLaunchInfo, phone ),
ARR_MEMBER( NetLaunchInfo, inviteID ),
};
static void
loadNLI( JNIEnv* env, NetLaunchInfo* nli, jobject jnli )
{
getInts( env, (void*)nli, jnli, nli_ints, VSIZE(nli_ints) );
getBools( env, (void*)nli, jnli, nli_bools, VSIZE(nli_bools) );
getStrings( env, (void*)nli, jnli, nli_strs, VSIZE(nli_strs) );
}
static void
setNLI( JNIEnv* env, jobject jnli, const NetLaunchInfo* nli )
{
setInts( env, jnli, (void*)nli, nli_ints, VSIZE(nli_ints) );
setBools( env, jnli, (void*)nli, nli_bools, VSIZE(nli_bools) );
setStrings( env, jnli, (void*)nli, nli_strs, VSIZE(nli_strs) );
}
static void static void
setJGI( JNIEnv* env, jobject jgi, const CurGameInfo* gi ) setJGI( JNIEnv* env, jobject jgi, const CurGameInfo* gi )
{ {
@ -453,6 +494,59 @@ Java_org_eehouse_android_xw4_jni_XwJNI_gi_1from_1stream
#endif #endif
} }
JNIEXPORT jbyteArray JNICALL
Java_org_eehouse_android_xw4_jni_XwJNI_nli_1to_1stream
( JNIEnv* env, jclass C, jobject njli )
{
LOG_FUNC();
jbyteArray result;
#ifdef MEM_DEBUG
MemPoolCtx* mpool = mpool_make( NULL );
#endif
NetLaunchInfo nli = {0};
loadNLI( env, &nli, njli );
/* CurGameInfo* gi = makeGI( MPPARM(mpool) env, jgi ); */
VTableMgr* vtMgr = make_vtablemgr( MPPARM_NOCOMMA(mpool) );
XWStreamCtxt* stream = mem_stream_make( MPPARM(mpool) vtMgr,
NULL, 0, NULL );
nli_saveToStream( &nli, stream );
result = streamToBArray( env, stream );
stream_destroy( stream );
vtmgr_destroy( MPPARM(mpool) vtMgr );
#ifdef MEM_DEBUG
mpool_destroy( mpool );
#endif
return result;
}
JNIEXPORT void JNICALL
Java_org_eehouse_android_xw4_jni_XwJNI_nli_1from_1stream
( JNIEnv* env, jclass C, jobject jnli, jbyteArray jstream )
{
LOG_FUNC();
#ifdef MEM_DEBUG
MemPoolCtx* mpool = mpool_make( NULL );
#endif
VTableMgr* vtMgr = make_vtablemgr( MPPARM_NOCOMMA(mpool) );
XWStreamCtxt* stream = streamFromJStream( MPPARM(mpool) env, vtMgr, jstream );
NetLaunchInfo nli = {0};
if ( nli_makeFromStream( &nli, stream ) ) {
setNLI( env, jnli, &nli );
} else {
XP_LOGF( "%s: game_makeFromStream failed", __func__ );
}
stream_destroy( stream );
vtmgr_destroy( MPPARM(mpool) vtMgr );
#ifdef MEM_DEBUG
mpool_destroy( mpool );
#endif
}
JNIEXPORT void JNICALL JNIEXPORT void JNICALL
Java_org_eehouse_android_xw4_jni_XwJNI_comms_1getInitialAddr Java_org_eehouse_android_xw4_jni_XwJNI_comms_1getInitialAddr
( JNIEnv* env, jclass C, jobject jaddr, jstring jname, jint port ) ( JNIEnv* env, jclass C, jobject jaddr, jstring jname, jint port )
@ -1730,6 +1824,25 @@ Java_org_eehouse_android_xw4_jni_XwJNI_comms_1isConnected
return result; return result;
} }
JNIEXPORT jstring JNICALL
Java_org_eehouse_android_xw4_jni_XwJNI_comms_1formatRelayID
( JNIEnv* env, jclass C, jint gamePtr, jint indx )
{
jstring result = NULL;
XWJNI_START();
XP_UCHAR buf[64];
XP_U16 len = sizeof(buf);
if ( comms_formatRelayID( state->game.comms, indx, buf, &len ) ) {
XP_ASSERT( len < sizeof(buf) );
LOG_RETURNF( "%s", buf );
result = (*env)->NewStringUTF( env, buf );
}
XWJNI_END();
return result;
}
JNIEXPORT jstring JNICALL JNIEXPORT jstring JNICALL
Java_org_eehouse_android_xw4_jni_XwJNI_comms_1getStats Java_org_eehouse_android_xw4_jni_XwJNI_comms_1getStats
( JNIEnv* env, jclass C, jint gamePtr ) ( JNIEnv* env, jclass C, jint gamePtr )

View file

@ -0,0 +1,2 @@
/armeabi
/x86

View file

@ -23,6 +23,7 @@
android:layout_width="fill_parent" android:layout_width="fill_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/game_locked" android:text="@string/game_locked"
android:checked="true"
android:visibility="gone" android:visibility="gone"
android:layout_alignParentTop="true" android:layout_alignParentTop="true"
/> />

View file

@ -0,0 +1,52 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<TextView android:id="@+id/invite_desc"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceMedium"
android:padding="8dp"
/>
<ListView android:id="@id/android:list"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:drawSelectorOnTop="false"
android:layout_weight="1"
android:padding="8dp"
/>
<TextView android:id="@android:id/empty"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/empty_relay_inviter"
android:padding="20dp"
/>
<LinearLayout android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
>
<Button android:id="@+id/button_add"
android:text="@string/button_relay_add"
style="@style/evenly_spaced_horizontal"
/>
<Button android:id="@+id/button_clear"
android:text="@string/bt_pick_clear_button"
style="@style/evenly_spaced_horizontal"
/>
</LinearLayout>
<Button android:id="@+id/button_invite"
android:text="@string/button_invite"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="10dip"
/>
</LinearLayout>

View file

@ -5,6 +5,10 @@
<group android:id="@+id/group_done"> <group android:id="@+id/group_done">
<!-- title set in BoardActivity --> <!-- title set in BoardActivity -->
<item android:id="@+id/board_menu_rematch"
android:title="@string/button_rematch"
android:showAsAction="ifRoom"
/>
<item android:id="@+id/board_menu_done" <item android:id="@+id/board_menu_done"
android:alphabeticShortcut="D" android:alphabeticShortcut="D"
android:showAsAction="ifRoom" android:showAsAction="ifRoom"

View file

@ -0,0 +1,34 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@+id/games_game_delete"
android:title="@string/list_item_delete"
/>
<item android:id="@+id/games_game_rematch"
android:title="@string/button_rematch"
/>
<item android:id="@+id/games_game_config"
android:title="@string/list_item_config"
/>
<item android:id="@+id/games_game_move"
android:title="@string/list_item_move"
/>
<item android:id="@+id/games_game_reset"
android:title="@string/list_item_reset"
/>
<item android:id="@+id/games_game_new_from"
android:title="@string/list_item_new_from"
/>
<item android:id="@+id/games_game_rename"
android:title="@string/list_item_rename"
/>
<item android:id="@+id/games_game_copy"
android:title="@string/list_item_copy"
/>
<item android:id="@+id/games_game_select"
android:title="@string/list_item_select"
/>
<item android:id="@+id/games_game_deselect"
android:title="@string/list_item_deselect"
/>
</menu>

View file

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@+id/games_game_select"
android:title="@string/list_item_select"
/>
<item android:id="@+id/games_game_deselect"
android:title="@string/list_item_deselect"
/>
<item android:id="@+id/games_group_moveup"
android:title="@string/list_group_moveup"
/>
<item android:id="@+id/games_group_movedown"
android:title="@string/list_group_movedown"
/>
<item android:id="@+id/games_group_delete"
android:title="@string/list_group_delete"
/>
<item android:id="@+id/games_group_default"
android:title="@string/list_group_default"
/>
<item android:id="@+id/games_group_rename"
android:title="@string/list_group_rename"
/>
</menu>

View file

@ -57,6 +57,9 @@
android:icon="@drawable/content_discard__gen" android:icon="@drawable/content_discard__gen"
android:showAsAction="ifRoom" android:showAsAction="ifRoom"
/> />
<item android:id="@+id/games_game_rematch"
android:title="@string/button_rematch"
/>
<item android:id="@+id/games_game_config" <item android:id="@+id/games_game_config"
android:title="@string/list_item_config" android:title="@string/list_item_config"
android:icon="@drawable/content_edit" android:icon="@drawable/content_edit"

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<string name="app_version">4.4 beta 98</string> <string name="app_version">4.4 beta 101</string>
</resources> </resources>

View file

@ -2,7 +2,6 @@
<!-- Resources in this file do not require localization --> <!-- Resources in this file do not require localization -->
<resources> <resources>
<!-- prefs keys --> <!-- prefs keys -->
<string name="key_color_tiles">key_color_tiles</string> <string name="key_color_tiles">key_color_tiles</string>
<string name="key_show_arrow">key_show_arrow</string> <string name="key_show_arrow">key_show_arrow</string>
@ -10,6 +9,7 @@
<string name="key_explain_robot">key_explain_robot</string> <string name="key_explain_robot">key_explain_robot</string>
<string name="key_skip_confirm">key_skip_confirm</string> <string name="key_skip_confirm">key_skip_confirm</string>
<string name="key_disable_nag">key_disable_nag</string> <string name="key_disable_nag">key_disable_nag</string>
<string name="key_disable_nag_solo">key_disable_nag_solo</string>
<string name="key_sort_tiles">key_sort_tiles</string> <string name="key_sort_tiles">key_sort_tiles</string>
<string name="key_peek_other">key_peek_other</string> <string name="key_peek_other">key_peek_other</string>
<string name="key_hide_crosshairs">key_hide_crosshairs</string> <string name="key_hide_crosshairs">key_hide_crosshairs</string>
@ -73,6 +73,7 @@
<string name="key_closed_langs">key_closed_langs</string> <string name="key_closed_langs">key_closed_langs</string>
<string name="key_bt_addrs">key_bt_addrs</string> <string name="key_bt_addrs">key_bt_addrs</string>
<string name="key_sms_phones">key_sms_phones</string> <string name="key_sms_phones">key_sms_phones</string>
<string name="key_relay_ids">key_relay_ids</string>
<string name="key_connstat_data">key_connstat_data</string> <string name="key_connstat_data">key_connstat_data</string>
<string name="key_dev_id">key_dev_id</string> <string name="key_dev_id">key_dev_id</string>
@ -119,6 +120,7 @@
<string name="key_enable_dup_invite">key_enable_dup_invite</string> <string name="key_enable_dup_invite">key_enable_dup_invite</string>
<string name="key_enable_nfc_toself">key_enable_nfc_toself</string> <string name="key_enable_nfc_toself">key_enable_nfc_toself</string>
<string name="key_enable_sms_toself">key_enable_sms_toself</string> <string name="key_enable_sms_toself">key_enable_sms_toself</string>
<string name="key_enable_relay_toself">key_enable_relay_toself</string>
<string name="key_nag_intervals">key_nag_intervals</string> <string name="key_nag_intervals">key_nag_intervals</string>
<string name="key_download_path">key_download_path</string> <string name="key_download_path">key_download_path</string>
<string name="key_got_langdict">key_got_langdict</string> <string name="key_got_langdict">key_got_langdict</string>
@ -126,6 +128,8 @@
<string name="key_xlations_enabled">key_xlations_enabled</string> <string name="key_xlations_enabled">key_xlations_enabled</string>
<string name="key_invite_multi">key_invite_multi</string> <string name="key_invite_multi">key_invite_multi</string>
<string name="key_notagain_enablepublic">key_notagain_enablepublic</string> <string name="key_notagain_enablepublic">key_notagain_enablepublic</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_bt">key_na_comms_bt</string>
<string name="key_na_comms_sms">key_na_comms_sms</string> <string name="key_na_comms_sms">key_na_comms_sms</string>
@ -263,7 +267,7 @@
<!-- --> <!-- -->
<item>DIEC</item> <item>DIEC</item>
<item>:ca:</item> <item>:ca:</item>
<item>http://mdlc.iec.cat/results.asp?txtEntrada=%2$s</item> <item>http://diccionari.totescrable.cat/cercador/?mot=%2$s</item>
<!-- --> <!-- -->
<item>Viccionari</item> <item>Viccionari</item>
<item>:ca:</item> <item>:ca:</item>

View file

@ -163,7 +163,7 @@
<plurals name="confirm_seldeletes_fmt"> <plurals name="confirm_seldeletes_fmt">
<item quantity="one">Are you sure you want to delete <item quantity="one">Are you sure you want to delete
the %1$d selected game? This action cannot be undone.</item> the selected game? This action cannot be undone.</item>
<item quantity="other">Are you sure you want to delete <item quantity="other">Are you sure you want to delete
the %1$d selected games? This action cannot be undone.</item> the %1$d selected games? This action cannot be undone.</item>
</plurals> </plurals>
@ -171,7 +171,7 @@
<!-- Text of confirmation dialog posted when list_item_reset menu <!-- Text of confirmation dialog posted when list_item_reset menu
is selected --> is selected -->
<plurals name="confirm_reset_fmt"> <plurals name="confirm_reset_fmt">
<item quantity="one">Are you sure you want to reset the %1$d <item quantity="one">Are you sure you want to reset the
selected game?\n\n(Resetting erases all moves and any connection selected game?\n\n(Resetting erases all moves and any connection
information.)</item> information.)</item>
<item quantity="other">Are you sure you want to reset the %1$d <item quantity="other">Are you sure you want to reset the %1$d
@ -696,7 +696,7 @@
because users should not have to do do this EVER. --> because users should not have to do do this EVER. -->
<string name="board_menu_game_resend">Resend messages</string> <string name="board_menu_game_resend">Resend messages</string>
<plurals name="resend_finished_fmt"> <plurals name="resend_finished_fmt">
<item quantity="one">Resend finished; sent %1$d message.</item> <item quantity="one">Resend finished; sent one message.</item>
<item quantity="other">Resend finished; sent %1$d messages.</item> <item quantity="other">Resend finished; sent %1$d messages.</item>
</plurals> </plurals>
@ -1191,6 +1191,7 @@
<string name="invite_choice_email">Email</string> <string name="invite_choice_email">Email</string>
<string name="invite_choice_bt">Bluetooth</string> <string name="invite_choice_bt">Bluetooth</string>
<string name="invite_choice_nfc">NFC (\"Android beaming\")</string> <string name="invite_choice_nfc">NFC (\"Android beaming\")</string>
<string name="invite_choice_relay">Internet/Relay</string>
<string name="invite_choice_title">Inviting players: How?</string> <string name="invite_choice_title">Inviting players: How?</string>
<!-- <string name="sms_or_email">Send invitation using SMS (texting) or --> <!-- <string name="sms_or_email">Send invitation using SMS (texting) or -->
<!-- via email?</string> --> <!-- via email?</string> -->
@ -1719,12 +1720,11 @@
--> -->
<!-- Welcome dialog title --> <!-- Welcome dialog title -->
<string name="default_name_title">Welcome</string> <string name="default_name_title">Default player name</string>
<!-- Welcome dialog text --> <!-- Welcome dialog text -->
<string name="default_name_message">Thanks for installing <string name="default_name_message">Please enter your name
Crosswords!\n\nFeel free to enter your name here. It will be used here. It will be used when creating new games. (You can change it
when creating new games. (You can change it later in the \"New later in the \"New game default\" section of Settings.)</string>
game default\" section of Settings.)</string>
<!-- <!--
########################################################### ###########################################################
@ -1738,7 +1738,7 @@
<string name="about_vers_fmt">Crosswords for Android, Version %1$s, <string name="about_vers_fmt">Crosswords for Android, Version %1$s,
rev %2$s, built on %3$s.</string> rev %2$s, built on %3$s.</string>
<!-- copyright info --> <!-- copyright info -->
<string name="about_copyright">Copyright (C) 1998-2015 by Eric <string name="about_copyright">Copyright (C) 1998-2016 by Eric
House. This free/open source software is released under the GNU Public House. This free/open source software is released under the GNU Public
License.</string> License.</string>
@ -1865,9 +1865,12 @@
<!-- --> <!-- -->
<string name="summary_conn">Game in play</string> <string name="summary_conn">Game in play</string>
<!-- --> <!-- -->
<string name="new_bt_title">New game via Bluetooth</string> <string name="invite_notice_title">New game via Invitation</string>
<!-- --> <!-- -->
<string name="new_bt_body_fmt">A player on the device %1$s wants to start a game</string> <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 <string name="bt_bad_proto_fmt">The version of Crosswords on
\"%1$s\" is incompatible with this one for play using \"%1$s\" is incompatible with this one for play using
@ -1905,6 +1908,7 @@
<string name="bt_invite_title">Bluetooth Invitation</string> <string name="bt_invite_title">Bluetooth Invitation</string>
<!-- Title of phone number picker during invitation to a game via SMS --> <!-- Title of phone number picker during invitation to a game via SMS -->
<string name="sms_invite_title">SMS Invitation</string> <string name="sms_invite_title">SMS Invitation</string>
<string name="relay_invite_title">Relay Invitation</string>
<!-- --> <!-- -->
<string name="game_btname_title">Bluetooth game name</string> <string name="game_btname_title">Bluetooth game name</string>
@ -1921,12 +1925,12 @@
<!-- --> <!-- -->
<string name="dft_sms_name_fmt">SMS Game %1$X</string> <string name="dft_sms_name_fmt">SMS Game %1$X</string>
<!-- --> <!-- -->
<string name="new_sms_title">New game via SMS</string>
<!-- -->
<string name="new_name_body_fmt">%1$s has invited you to play</string> <string name="new_name_body_fmt">%1$s has invited you to play</string>
<!-- --> <!-- -->
<string name="button_sms_add">Import contact</string> <string name="button_sms_add">Import contact</string>
<!-- --> <!-- -->
<string name="button_relay_add">Scan games</string>
<!-- -->
<plurals name="invite_sms_desc_fmt"> <plurals name="invite_sms_desc_fmt">
<item quantity="one">Please check the phone number you want to <item quantity="one">Please check the phone number you want to
invite to your new game, then tap \"%2$s\".</item> invite to your new game, then tap \"%2$s\".</item>
@ -1934,6 +1938,13 @@
want to invite to your new game, then tap \"%2$s\".</item> want to invite to your new game, then tap \"%2$s\".</item>
</plurals> </plurals>
<!-- --> <!-- -->
<plurals name="invite_relay_desc_fmt">
<item quantity="one">Please check the device you want to invite
to your new game, then tap \"%2$s\".</item>
<item quantity="other">Please check the %1$d devices you want to invite
to your new game, then tap \"%2$s\".</item>
</plurals>
<!-- -->
<string name="manual_owner_name">(Not in contacts)</string> <string name="manual_owner_name">(Not in contacts)</string>
<!-- --> <!-- -->
<string name="warn_nomobile_fmt">The number %1$s for %2$s is not <string name="warn_nomobile_fmt">The number %1$s for %2$s is not
@ -1943,14 +1954,21 @@
\"Import contact\" button to add people you want to invite, the + \"Import contact\" button to add people you want to invite, the +
button to enter numbers directly.</string> button to enter numbers directly.</string>
<!-- --> <!-- -->
<string name="get_sms_number">Enter phone number:</string> <string name="empty_relay_inviter">This list of devies is
empty. Use the \"Scan games\" button to scan your old games
for opponents. Use the + button to enter device IDs directly.</string>
<!-- --> <!-- -->
<plurals name="confirm_clear_fmt"> <string name="get_sms_number">Enter phone number:</string>
<string name="get_relay_number">Enter device ID:</string>
<!-- -->
<plurals name="confirm_clear_sms_fmt">
<item quantity="one">Are you sure you want to delete the checked <item quantity="one">Are you sure you want to delete the checked
phone number?</item> phone number?</item>
<item quantity="other">Are you sure you want to delete the <item quantity="other">Are you sure you want to delete the
%1$d checked phone numbers?</item> %1$d checked phone numbers?</item>
</plurals> </plurals>
<string name="confirm_clear_relay">Are you sure you want to delete the
checked device[s]?</string>
<!-- --> <!-- -->
<string name="connect_label_sms">Connection (via SMS/text)</string> <string name="connect_label_sms">Connection (via SMS/text)</string>
<!-- --> <!-- -->
@ -2379,6 +2397,7 @@
<string name="new_game">New one-device game</string> <string name="new_game">New one-device game</string>
<string name="new_game_networked">New networked game</string> <string name="new_game_networked">New networked game</string>
<string name="rematch_name_fmt">Rematch: %1$s</string>
<string name="new_game_message">Would you like to create this game <string name="new_game_message">Would you like to create this game
using default settings?\n\nOr would you like to configure it using default settings?\n\nOr would you like to configure it
@ -2419,6 +2438,8 @@
</string> </string>
<string name="waiting_title">Waiting for players</string> <string name="waiting_title">Waiting for players</string>
<!-- Button for alert with title above -->
<string name="button_wait">Wait</string>
<string name="invite_stays">(This dialog will stay up until all <string name="invite_stays">(This dialog will stay up until all
remote players have connected. You can close the game if you remote players have connected. You can close the game if you
@ -2450,9 +2471,11 @@
and want them back, enable them now. You can turn them off again and want them back, enable them now. You can turn them off again
in Settings.</string> in Settings.</string>
<string name="disable_nag_title">Disable turn reminders</string> <string name="disable_nags_title">Turn reminders</string>
<string name="disable_nag_title">Disable network game reminders</string>
<string name="disable_nag_summary">Do not notify me no matter <string name="disable_nag_summary">Do not notify me no matter
how long it\'s been my turn</string> how long it\'s been my turn</string>
<string name="disable_nag_solo_title">Disable solo game reminders</string>
<string name="confirm_get_locdict_fmt">Your device is set up for <string name="confirm_get_locdict_fmt">Your device is set up for
%1$s. Would you like to download a wordlist so you can play %1$s. Would you like to download a wordlist so you can play
@ -2476,6 +2499,7 @@
<string name="netstats_title">Game network stats</string> <string name="netstats_title">Game network stats</string>
<string name="git_rev_title">Source version id</string> <string name="git_rev_title">Source version id</string>
<string name="devid_title">Device ID (on relay)</string>
<string name="relay_port">Relay game port</string> <string name="relay_port">Relay game port</string>
<string name="proxy_port">Relay device port</string> <string name="proxy_port">Relay device port</string>
<string name="name_dict_fmt">%1$s/%2$s</string> <string name="name_dict_fmt">%1$s/%2$s</string>
@ -2562,6 +2586,14 @@
<string name="str_no_hint_found">Cannot find any moves</string> <string name="str_no_hint_found">Cannot find any moves</string>
<string name="not_again_rematch_two_only">The Rematch button is
disabled because, for now anyway, rematch is limited to two-person
games. I think it\'s rare that people play with more than two. Let
me know if I\'m wrong and I\'ll try harder to make it work.</string>
<string name="enable_relay_toself_title">Enable relay invites to self</string>
<string name="enable_relay_toself_summary">(To aid testing and debugging)</string>
<!-- Shown after "resend messages" menuitem chosen --> <!-- Shown after "resend messages" menuitem chosen -->
<plurals name="resent_msgs_fmt"> <plurals name="resent_msgs_fmt">
<item quantity="one">One message sent</item> <item quantity="one">One message sent</item>
@ -2571,8 +2603,7 @@
<string name="confirm_clear_chat">Are you sure you want to delete <string name="confirm_clear_chat">Are you sure you want to delete
all chat history for this game?\n\n(This action cannot be all chat history for this game?\n\n(This action cannot be
undone.)</string> undone.)</string>
<!-- EXPERIMENTAL: Shown as toast when user chooses "Copy to
clipboard" for invitation -->
<string name="invite_copied">Invitation ready to paste</string> <string name="invite_copied">Invitation ready to paste</string>
<!-- EXPERIMENTAL: "label" for invite on clipboard. If it's shown <!-- EXPERIMENTAL: "label" for invite on clipboard. If it's shown
it's by some Android utility --> it's by some Android utility -->
@ -2582,4 +2613,19 @@
<string name="not_again_clip_expl_fmt">The \"%1$s\" option copies an <string name="not_again_clip_expl_fmt">The \"%1$s\" option copies an
invitation URL to the clipboard. Paste it into the app of your invitation URL to the clipboard. Paste it into the app of your
choice and send it to your friend.</string> choice and send it to your friend.</string>
<string name="confirm_clear_chat">Are you sure you want to delete
all chat history for this game?\n\n(This action cannot be
undone.)</string>
<string name="rel_invite_title">Relay invite title</string>
<string name="fetching_from_relay">Fetching games from relay</string>
<string name="processing_games">Processing games</string>
<string name="list_item_select">Select</string>
<string name="list_item_deselect">De-select</string>
<string name="not_again_dfltname_fmt">You are using the default
player name \"%1$s\". Would you like to personalize with your own
name before you create this game?</string>
</resources> </resources>

View file

@ -231,11 +231,22 @@
android:summary="@string/skip_confirm_turn_summary" android:summary="@string/skip_confirm_turn_summary"
android:defaultValue="false" android:defaultValue="false"
/> />
<CheckBoxPreference android:key="@string/key_disable_nag"
android:title="@string/disable_nag_title" <PreferenceScreen android:title="@string/disable_nags_title"
android:summary="@string/disable_nag_summary" >
android:defaultValue="false"
/> <CheckBoxPreference android:key="@string/key_disable_nag"
android:title="@string/disable_nag_title"
android:summary="@string/disable_nag_summary"
android:defaultValue="false"
/>
<CheckBoxPreference android:key="@string/key_disable_nag_solo"
android:title="@string/disable_nag_solo_title"
android:summary="@string/disable_nag_summary"
android:defaultValue="true"
/>
</PreferenceScreen>
<CheckBoxPreference android:key="@string/key_default_loc" <CheckBoxPreference android:key="@string/key_default_loc"
android:title="@string/default_loc" android:title="@string/default_loc"
android:summary="@string/default_loc_summary" android:summary="@string/default_loc_summary"
@ -394,6 +405,11 @@
android:defaultValue="10998" android:defaultValue="10998"
android:numeric="decimal" android:numeric="decimal"
/> />
<CheckBoxPreference android:key="@string/key_enable_relay_toself"
android:title="@string/enable_relay_toself_title"
android:summary="@string/enable_relay_toself_summary"
android:defaultValue="false"
/>
</PreferenceScreen> </PreferenceScreen>
<PreferenceScreen android:title="@string/pref_group_l10n_title" <PreferenceScreen android:title="@string/pref_group_l10n_title"
@ -454,5 +470,9 @@
android:summary="@string/git_rev" android:summary="@string/git_rev"
android:enabled="false" android:enabled="false"
/> />
<org.eehouse.android.xw4.XWDevIDPreference
android:title="@string/devid_title"
android:enabled="false"
/>
</PreferenceScreen> </PreferenceScreen>
</PreferenceScreen> </PreferenceScreen>

View file

@ -2371,9 +2371,6 @@ fois. Ouvrez à nouveau la partie pour réessayer.</string>
<!--<string name="dft_sms_name_fmt">SMS Game %1$X</string> --> <!--<string name="dft_sms_name_fmt">SMS Game %1$X</string> -->
<string name="dft_sms_name_fmt">Partie %1$X par SMS</string> <string name="dft_sms_name_fmt">Partie %1$X par SMS</string>
<!-- --> <!-- -->
<!--<string name="new_sms_title">New game via SMS</string>-->
<string name="new_sms_title">Nouvelle partie par SMS</string>
<!-- -->
<!--<string name="new_name_body_fmt">%1$s has invited you to play</string>--> <!--<string name="new_name_body_fmt">%1$s has invited you to play</string>-->
<string name="new_name_body_fmt">%1$s vous a invité à jouer</string> <string name="new_name_body_fmt">%1$s vous a invité à jouer</string>
<!-- --> <!-- -->
@ -2411,7 +2408,7 @@ inviter, ou le bouton + pour saisir des numéros directement.</string>
<!-- --> <!-- -->
<!--<string name="confirm_clear">Are you sure you want to delete the <!--<string name="confirm_clear">Are you sure you want to delete the
checked phone number[s]?</string>--> checked phone number[s]?</string>-->
<string name="confirm_clear">Êtes-vous sûr de vouloir effacer les numéros <string name="confirm_clear_sms">Êtes-vous sûr de vouloir effacer les numéros
de téléphone cochés ?</string> de téléphone cochés ?</string>
<!-- --> <!-- -->
<!--<string name="connect_label_sms">Connection (via SMS/text)</string>--> <!--<string name="connect_label_sms">Connection (via SMS/text)</string>-->

View file

@ -1586,7 +1586,7 @@
<!-- --> <!-- -->
<string name="dft_sms_name_fmt">Jogo SMS %X</string> <string name="dft_sms_name_fmt">Jogo SMS %X</string>
<!-- --> <!-- -->
<string name="new_sms_title">Novo jogo por SMS</string> <string name="invite_notice_title">Novo jogo</string>
<!-- --> <!-- -->
<string name="new_name_body_fmt">%1$s te convidou para jogar</string> <string name="new_name_body_fmt">%1$s te convidou para jogar</string>
<!-- --> <!-- -->
@ -1606,8 +1606,6 @@
<!-- --> <!-- -->
<string name="get_sms_number">Entre o número telefônico:</string> <string name="get_sms_number">Entre o número telefônico:</string>
<!-- --> <!-- -->
<!-- -->
<!-- -->
<string name="connect_label_sms">Conexão (por SMS/texto)</string> <string name="connect_label_sms">Conexão (por SMS/texto)</string>
<!-- --> <!-- -->
<string name="phone_label">Número(s) conectado(s):</string> <string name="phone_label">Número(s) conectado(s):</string>

View file

@ -1243,7 +1243,7 @@
<!-- --> <!-- -->
<string name="invite_success">XLATE ME: Invitation received.</string> <string name="invite_success">XLATE ME: Invitation received.</string>
<!-- --> <!-- -->
<string name="confirm_clear">XLATE ME: Are you sure you want to delete the <string name="confirm_clear_sms">XLATE ME: Are you sure you want to delete the
checked phone number[s]?</string> checked phone number[s]?</string>
<!-- --> <!-- -->
<string name="get_sms_number">XLATE ME: Enter phone number:</string> <string name="get_sms_number">XLATE ME: Enter phone number:</string>
@ -1267,8 +1267,6 @@
<!-- --> <!-- -->
<string name="new_name_body_fmt">XLATE ME: %1$s has invited you to play</string> <string name="new_name_body_fmt">XLATE ME: %1$s has invited you to play</string>
<!-- --> <!-- -->
<string name="new_sms_title">XLATE ME: New game via SMS</string>
<!-- -->
<string name="dft_sms_name_fmt">XLATE ME: SMS Game %1$X</string> <string name="dft_sms_name_fmt">XLATE ME: SMS Game %1$X</string>
<!-- --> <!-- -->
<string name="sms_disabled">XLATE ME: Playing via SMS is currently disabled. <string name="sms_disabled">XLATE ME: Playing via SMS is currently disabled.

View file

@ -0,0 +1 @@
include ':app'

View file

@ -54,15 +54,15 @@ public class BTInviteDelegate extends InviteDelegate {
private BTDevsAdapter m_adapter; private BTDevsAdapter m_adapter;
public static void launchForResult( Activity activity, int nMissing, public static void launchForResult( Activity activity, int nMissing,
int requestCode ) RequestCode requestCode )
{ {
Assert.assertTrue( 0 < nMissing ); // don't call if nMissing == 0 Assert.assertTrue( 0 < nMissing ); // don't call if nMissing == 0
Intent intent = new Intent( activity, BTInviteActivity.class ); Intent intent = new Intent( activity, BTInviteActivity.class );
intent.putExtra( INTENT_KEY_NMISSING, nMissing ); intent.putExtra( INTENT_KEY_NMISSING, nMissing );
activity.startActivityForResult( intent, requestCode ); activity.startActivityForResult( intent, requestCode.ordinal() );
} }
protected BTInviteDelegate( ListDelegator delegator, Bundle savedInstanceState ) protected BTInviteDelegate( Delegator delegator, Bundle savedInstanceState )
{ {
super( delegator, savedInstanceState, R.layout.btinviter ); super( delegator, savedInstanceState, R.layout.btinviter );
m_activity = delegator.getActivity(); m_activity = delegator.getActivity();

View file

@ -42,6 +42,7 @@ import java.util.Iterator;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.ListIterator; import java.util.ListIterator;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@ -51,16 +52,21 @@ import org.eehouse.android.xw4.MultiService.MultiEvent;
import org.eehouse.android.xw4.jni.CommsAddrRec.CommsConnType; import org.eehouse.android.xw4.jni.CommsAddrRec.CommsConnType;
import org.eehouse.android.xw4.jni.CommsAddrRec; import org.eehouse.android.xw4.jni.CommsAddrRec;
import org.eehouse.android.xw4.jni.LastMoveInfo; import org.eehouse.android.xw4.jni.LastMoveInfo;
import org.eehouse.android.xw4.jni.XwJNI;
import org.eehouse.android.xw4.loc.LocUtils; import org.eehouse.android.xw4.loc.LocUtils;
import junit.framework.Assert; import junit.framework.Assert;
public class BTService extends XWService { public class BTService extends XWService {
private static final String BOGUS_MARSHMALLOW_ADDR = "02:00:00:00:00:00";
private static final long RESEND_TIMEOUT = 5; // seconds private static final long RESEND_TIMEOUT = 5; // seconds
private static final int MAX_SEND_FAIL = 3; private static final int MAX_SEND_FAIL = 3;
private static final int BT_PROTO = 1; // using jsons instead of lots of fields private static final int BT_PROTO_ORIG = 0;
private static final int BT_PROTO_JSONS = 1; // using jsons instead of lots of fields
private static final int BT_PROTO_NLI = 2; // using binary/common form of NLI
private static final int BT_PROTO = BT_PROTO_JSONS; // change in a release or two
private enum BTAction { _NONE, private enum BTAction { _NONE,
SCAN, SCAN,
@ -105,6 +111,7 @@ public class BTService extends XWService {
MESG_DECL, MESG_DECL,
MESG_GAMEGONE, MESG_GAMEGONE,
REMOVE_FOR, REMOVE_FOR,
INVITE_DUP_INVITE,
}; };
private class BTQueueElem { private class BTQueueElem {
@ -134,22 +141,30 @@ public class BTService extends XWService {
Assert.assertTrue( null != btAddr && 0 < btAddr.length() ); Assert.assertTrue( null != btAddr && 0 < btAddr.length() );
m_msg = buf; m_btAddr = btAddr; m_msg = buf; m_btAddr = btAddr;
m_gameID = gameID; m_gameID = gameID;
checkAddr();
} }
public BTQueueElem( BTCmd cmd, String btAddr, int gameID ) { public BTQueueElem( BTCmd cmd, String btAddr, int gameID ) {
this( cmd ); this( cmd );
Assert.assertTrue( null != btAddr && 0 < btAddr.length() ); Assert.assertTrue( null != btAddr && 0 < btAddr.length() );
m_btAddr = btAddr; m_btAddr = btAddr;
m_gameID = gameID; m_gameID = gameID;
checkAddr();
} }
public BTQueueElem( BTCmd cmd, NetLaunchInfo nli, String btAddr ) { public BTQueueElem( BTCmd cmd, NetLaunchInfo nli, String btAddr ) {
this( cmd ); this( cmd );
m_nli = nli; m_nli = nli;
m_btAddr = btAddr; m_btAddr = btAddr;
checkAddr();
} }
public int incrFailCount() { return ++m_failCount; } public int incrFailCount() { return ++m_failCount; }
public boolean failCountExceeded() { return m_failCount >= MAX_SEND_FAIL; } public boolean failCountExceeded() { return m_failCount >= MAX_SEND_FAIL; }
private void checkAddr()
{
Assert.assertFalse( BOGUS_MARSHMALLOW_ADDR.equals( m_btAddr ) );
}
} }
private BluetoothAdapter m_adapter; private BluetoothAdapter m_adapter;
@ -276,6 +291,7 @@ public class BTService extends XWService {
public static void inviteRemote( Context context, String btAddr, public static void inviteRemote( Context context, String btAddr,
NetLaunchInfo nli ) NetLaunchInfo nli )
{ {
Assert.assertTrue( null != btAddr && 0 < btAddr.length() );
Intent intent = getIntentTo( context, BTAction.INVITE ); Intent intent = getIntentTo( context, BTAction.INVITE );
String nliData = nli.toString(); String nliData = nli.toString();
intent.putExtra( GAMEDATA_KEY, nliData ); intent.putExtra( GAMEDATA_KEY, nliData );
@ -298,18 +314,23 @@ public class BTService extends XWService {
} }
public static int enqueueFor( Context context, byte[] buf, public static int enqueueFor( Context context, byte[] buf,
String targetAddr, int gameID ) CommsAddrRec targetAddr, int gameID )
{ {
int nSent = -1; int nSent = -1;
if ( null != targetAddr && 0 < targetAddr.length() ) { Assert.assertNotNull( targetAddr );
String btAddr = getSafeAddr( targetAddr );
if ( null != btAddr && 0 < btAddr.length() ) {
Intent intent = getIntentTo( context, BTAction.SEND ); Intent intent = getIntentTo( context, BTAction.SEND );
intent.putExtra( MSG_KEY, buf ); intent.putExtra( MSG_KEY, buf );
intent.putExtra( ADDR_KEY, targetAddr ); intent.putExtra( ADDR_KEY, btAddr );
intent.putExtra( GAMEID_KEY, gameID ); intent.putExtra( GAMEID_KEY, gameID );
context.startService( intent ); context.startService( intent );
nSent = buf.length; nSent = buf.length;
} else { }
DbgUtils.logf( "BTService.enqueueFor(): targetAddr is null" );
if ( -1 == nSent ) {
DbgUtils.logf( "BTService.enqueueFor(): can't send to %s",
targetAddr.bt_hostName );
} }
return nSent; return nSent;
} }
@ -373,12 +394,6 @@ public class BTService extends XWService {
String jsonData = intent.getStringExtra( GAMEDATA_KEY ); String jsonData = intent.getStringExtra( GAMEDATA_KEY );
NetLaunchInfo nli = new NetLaunchInfo( this, jsonData ); NetLaunchInfo nli = new NetLaunchInfo( this, jsonData );
DbgUtils.logf( "onStartCommand: nli: %s", nli.toString() ); DbgUtils.logf( "onStartCommand: nli: %s", nli.toString() );
// int gameID = intent.getIntExtra( GAMEID_KEY, -1 );
// String btAddr = intent.getStringExtra( ADDR_KEY );
// String gameName = intent.getStringExtra( GAMENAME_KEY );
// int lang = intent.getIntExtra( LANG_KEY, -1 );
// String dict = intent.getStringExtra( DICT_KEY );
// int nPlayersT = intent.getIntExtra( NTO_KEY, -1 );
String btAddr = intent.getStringExtra( ADDR_KEY ); String btAddr = intent.getStringExtra( ADDR_KEY );
m_sender.add( new BTQueueElem( BTCmd.INVITE, nli, btAddr ) ); m_sender.add( new BTQueueElem( BTCmd.INVITE, nli, btAddr ) );
break; break;
@ -457,42 +472,39 @@ public class BTService extends XWService {
} }
while ( null != m_serverSocket && m_adapter.isEnabled() ) { while ( null != m_serverSocket && m_adapter.isEnabled() ) {
BluetoothSocket socket = null;
DataInputStream inStream = null;
int nRead = 0;
try { try {
socket = m_serverSocket.accept(); // blocks BluetoothSocket socket = m_serverSocket.accept(); // blocks
addAddr( socket ); addAddr( socket );
inStream = new DataInputStream( socket.getInputStream() ); DataInputStream inStream =
new DataInputStream( socket.getInputStream() );
byte proto = inStream.readByte(); byte proto = inStream.readByte();
if ( proto != BT_PROTO ) { BTCmd cmd = BTCmd.values()[inStream.readByte()];
DataOutputStream os = new DataOutputStream( socket.getOutputStream() ); if ( protoOK( proto, cmd ) ) {
os.writeByte( BTCmd.BAD_PROTO.ordinal() );
os.flush();
socket.close();
sendBadProto( socket );
} else {
byte msg = inStream.readByte();
BTCmd cmd = BTCmd.values()[msg];
switch( cmd ) { switch( cmd ) {
case PING: case PING:
receivePing( socket ); receivePing( socket );
break; break;
case INVITE: case INVITE:
receiveInvitation( inStream, socket ); receiveInvitation( proto, inStream, socket );
break; break;
case MESG_SEND: case MESG_SEND:
receiveMessage( inStream, socket ); receiveMessage( inStream, socket );
break; break;
default: default:
DbgUtils.logf( "unexpected msg %d", msg ); DbgUtils.logf( "unexpected msg %s", cmd.toString());
break; break;
} }
updateStatusIn( true ); updateStatusIn( true );
} else {
DataOutputStream os =
new DataOutputStream( socket.getOutputStream() );
os.writeByte( BTCmd.BAD_PROTO.ordinal() );
os.flush();
socket.close();
sendBadProto( socket );
} }
} catch ( IOException ioe ) { } catch ( IOException ioe ) {
DbgUtils.logf( "trying again..." ); DbgUtils.logf( "trying again..." );
@ -524,6 +536,12 @@ public class BTService extends XWService {
interrupt(); interrupt();
} }
private boolean protoOK( byte proto, BTCmd cmd )
{
boolean ok = proto == BT_PROTO_NLI || proto == BT_PROTO_JSONS;
return ok;
}
private void receivePing( BluetoothSocket socket ) throws IOException private void receivePing( BluetoothSocket socket ) throws IOException
{ {
DataInputStream inStream = new DataInputStream( socket.getInputStream() ); DataInputStream inStream = new DataInputStream( socket.getInputStream() );
@ -539,13 +557,21 @@ public class BTService extends XWService {
updateStatusOut( true ); updateStatusOut( true );
} }
private void receiveInvitation( DataInputStream is, private void receiveInvitation( byte proto, DataInputStream is,
BluetoothSocket socket ) BluetoothSocket socket )
throws IOException throws IOException
{ {
BTCmd result; BTCmd result;
String asJson = is.readUTF(); NetLaunchInfo nli;
NetLaunchInfo nli = new NetLaunchInfo( BTService.this, asJson ); if ( BT_PROTO_JSONS == proto ) {
String asJson = is.readUTF();
nli = new NetLaunchInfo( BTService.this, asJson );
} else {
short len = is.readShort();
byte[] nliData = new byte[len];
is.read( nliData );
nli = XwJNI.nliFromStream( nliData );
}
BluetoothDevice host = socket.getRemoteDevice(); BluetoothDevice host = socket.getRemoteDevice();
addAddr( host ); addAddr( host );
@ -644,6 +670,32 @@ public class BTService extends XWService {
return m_addrs.contains( btAddr ); return m_addrs.contains( btAddr );
} }
private static Map<String, String> s_namesToAddrs;
private static String getSafeAddr( CommsAddrRec addr )
{
String btAddr = addr.bt_btAddr;
if ( BOGUS_MARSHMALLOW_ADDR.equals( btAddr ) ) {
String btName = addr.bt_hostName;
if ( null == s_namesToAddrs ) {
s_namesToAddrs = new HashMap<String, String>();
}
if ( ! s_namesToAddrs.containsKey( btName ) ) {
BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
if ( null != adapter ) {
Set<BluetoothDevice> devs = adapter.getBondedDevices();
Iterator<BluetoothDevice> iter = devs.iterator();
while ( iter.hasNext() ) {
BluetoothDevice dev = iter.next();
s_namesToAddrs.put( dev.getName(), dev.getAddress() );
}
}
}
btAddr = s_namesToAddrs.get( btName );
}
return btAddr;
}
private void clearDevs( String[] btAddrs ) private void clearDevs( String[] btAddrs )
{ {
if ( null != btAddrs ) { if ( null != btAddrs ) {
@ -802,7 +854,13 @@ public class BTService extends XWService {
BTCmd reply = null; BTCmd reply = null;
DataOutputStream outStream = connect( socket, BTCmd.INVITE ); DataOutputStream outStream = connect( socket, BTCmd.INVITE );
if ( null != outStream ) { if ( null != outStream ) {
outStream.writeUTF( elem.m_nli.toString() ); if ( BT_PROTO == BT_PROTO_JSONS ) {
outStream.writeUTF( elem.m_nli.toString() );
} else {
byte[] nliData = XwJNI.nliToStream( elem.m_nli );
outStream.writeShort( nliData.length );
outStream.write( nliData, 0, nliData.length );
}
DbgUtils.logf( "<eeh>sending invite for %d players", elem.m_nPlayersH ); DbgUtils.logf( "<eeh>sending invite for %d players", elem.m_nPlayersH );
outStream.flush(); outStream.flush();
@ -1060,16 +1118,20 @@ public class BTService extends XWService {
String btAddr ) String btAddr )
{ {
BTCmd result; BTCmd result;
if ( DictLangCache.haveDict( this, nli.lang, nli.dict ) ) { if ( checkNotDupe( nli ) ) {
result = makeGame( nli, btName, btAddr ); if ( DictLangCache.haveDict( this, nli.lang, nli.dict ) ) {
result = makeGame( nli, btName, btAddr );
} else {
Intent intent = MultiService
.makeMissingDictIntent( this, nli,
DictFetchOwner.OWNER_BT );
// NetLaunchInfo.putExtras( intent, gameID, btName, btAddr );
MultiService.postMissingDictNotification( this, intent,
nli.gameID() );
result = BTCmd.INVITE_ACCPT; // ???
}
} else { } else {
Intent intent = MultiService result = BTCmd.INVITE_DUP_INVITE; // dupe of rematch
.makeMissingDictIntent( this, nli,
DictFetchOwner.OWNER_BT );
// NetLaunchInfo.putExtras( intent, gameID, btName, btAddr );
MultiService.postMissingDictNotification( this, intent,
nli.gameID() );
result = BTCmd.INVITE_ACCPT; // ???
} }
return result; return result;
} }
@ -1081,7 +1143,8 @@ public class BTService extends XWService {
long[] rowids = DBUtils.getRowIDsFor( BTService.this, nli.gameID() ); long[] rowids = DBUtils.getRowIDsFor( BTService.this, nli.gameID() );
if ( null == rowids || 0 == rowids.length ) { if ( null == rowids || 0 == rowids.length ) {
CommsAddrRec addr = nli.makeAddrRec( BTService.this ); CommsAddrRec addr = nli.makeAddrRec( BTService.this );
long rowid = GameUtils.makeNewMultiGame( BTService.this, nli, m_btMsgSink ); long rowid = GameUtils.makeNewMultiGame( BTService.this, nli,
m_btMsgSink, null );
if ( DBUtils.ROWID_NOTFOUND == rowid ) { if ( DBUtils.ROWID_NOTFOUND == rowid ) {
result = BTCmd.INVITE_FAILED; result = BTCmd.INVITE_FAILED;
} else { } else {
@ -1092,7 +1155,10 @@ public class BTService extends XWService {
String body = LocUtils.getString( BTService.this, String body = LocUtils.getString( BTService.this,
R.string.new_bt_body_fmt, R.string.new_bt_body_fmt,
sender ); sender );
postNotification( nli.gameID(), R.string.new_bt_title, body, rowid );
GameUtils.postInvitedNotification( this, nli.gameID(), body,
rowid );
sendResult( MultiEvent.BT_GAME_CREATED, rowid ); sendResult( MultiEvent.BT_GAME_CREATED, rowid );
} }
} else { } else {
@ -1137,14 +1203,6 @@ public class BTService extends XWService {
sendResult( MultiEvent.BAD_PROTO_BT, socket.getRemoteDevice().getName() ); sendResult( MultiEvent.BAD_PROTO_BT, socket.getRemoteDevice().getName() );
} }
private void postNotification( int gameID, int title, String body,
long rowid )
{
Intent intent = GamesListDelegate.makeGameIDIntent( this, gameID );
Utils.postNotification( this, intent, R.string.new_btmove_title,
body, (int)rowid );
}
private void updateStatusOut( boolean success ) private void updateStatusOut( boolean success )
{ {
ConnStatusHandler ConnStatusHandler
@ -1191,9 +1249,11 @@ public class BTService extends XWService {
@Override @Override
public int sendViaBluetooth( byte[] buf, int gameID, CommsAddrRec addr ) public int sendViaBluetooth( byte[] buf, int gameID, CommsAddrRec addr )
{ {
String btAddr = getSafeAddr( addr );
Assert.assertTrue( addr.contains( CommsConnType.COMMS_CONN_BT ) ); Assert.assertTrue( addr.contains( CommsConnType.COMMS_CONN_BT ) );
m_sender.add( new BTQueueElem( BTCmd.MESG_SEND, buf, m_sender.add( new BTQueueElem( BTCmd.MESG_SEND, buf,
addr.bt_btAddr, gameID ) ); btAddr, gameID ) );
return buf.length; return buf.length;
} }
} }

View file

@ -23,8 +23,9 @@ package org.eehouse.android.xw4;
import android.app.Activity; import android.app.Activity;
import android.app.AlertDialog; import android.app.AlertDialog;
import android.app.Dialog; import android.app.Dialog;
import android.content.DialogInterface.OnClickListener; import android.content.Context;
import android.content.DialogInterface.OnCancelListener; import android.content.DialogInterface.OnCancelListener;
import android.content.DialogInterface.OnClickListener;
import android.content.DialogInterface.OnDismissListener; import android.content.DialogInterface.OnDismissListener;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.content.Intent; import android.content.Intent;
@ -68,10 +69,6 @@ public class BoardDelegate extends DelegateBase
public static final String INTENT_KEY_CHAT = "chat"; public static final String INTENT_KEY_CHAT = "chat";
private static final int CHAT_REQUEST = 1;
private static final int BT_INVITE_RESULT = 2;
private static final int SMS_INVITE_RESULT = 3;
private static final int SCREEN_ON_TIME = 10 * 60 * 1000; // 10 mins private static final int SCREEN_ON_TIME = 10 * 60 * 1000; // 10 mins
private static final String DLG_TITLE = "DLG_TITLE"; private static final String DLG_TITLE = "DLG_TITLE";
@ -236,11 +233,12 @@ public class BoardDelegate extends DelegateBase
} }
}; };
ab.setNegativeButton( R.string.button_retry, lstnr ); ab.setNegativeButton( R.string.button_retry, lstnr );
} else if ( DlgID.GAME_OVER == dlgID && rematchSupported() ) { } else if ( DlgID.GAME_OVER == dlgID
&& rematchSupported( true ) ) {
lstnr = new OnClickListener() { lstnr = new OnClickListener() {
public void onClick( DialogInterface dlg, public void onClick( DialogInterface dlg,
int whichButton ) { int whichButton ) {
doRematch(); doRematchIf();
} }
}; };
ab.setNegativeButton( R.string.button_rematch, lstnr ); ab.setNegativeButton( R.string.button_rematch, lstnr );
@ -450,7 +448,7 @@ public class BoardDelegate extends DelegateBase
dialog = ab.setTitle( "foo" ) dialog = ab.setTitle( "foo" )
.setMessage( "" ) .setMessage( "" )
.setPositiveButton( "", lstnr ) .setPositiveButton( "", lstnr )
.setNegativeButton( R.string.button_close_game, lstnr2 ) .setNegativeButton( R.string.button_wait, lstnr2 )
.setOnCancelListener( new OnCancelListener() { .setOnCancelListener( new OnCancelListener() {
public void onCancel( DialogInterface dialog ) { public void onCancel( DialogInterface dialog ) {
finish(); finish();
@ -641,9 +639,11 @@ public class BoardDelegate extends DelegateBase
} }
} }
protected void onActivityResult( int requestCode, int resultCode, Intent data ) @Override
protected void onActivityResult( RequestCode requestCode, int resultCode, Intent data )
{ {
if ( Activity.RESULT_CANCELED != resultCode ) { if ( Activity.RESULT_CANCELED != resultCode ) {
InviteMeans missingMeans = null;
switch ( requestCode ) { switch ( requestCode ) {
case CHAT_REQUEST: case CHAT_REQUEST:
if ( BuildConstants.CHAT_SUPPORTED ) { if ( BuildConstants.CHAT_SUPPORTED ) {
@ -655,14 +655,22 @@ public class BoardDelegate extends DelegateBase
} }
break; break;
case BT_INVITE_RESULT: case BT_INVITE_RESULT:
missingMeans = InviteMeans.BLUETOOTH;
break;
case SMS_INVITE_RESULT: case SMS_INVITE_RESULT:
// onActivityResult is called immediately *before* missingMeans = InviteMeans.SMS;
// onResume -- meaning m_gi etc are still null. break;
case RELAY_INVITE_RESULT:
missingMeans = InviteMeans.RELAY;
break;
}
if ( null != missingMeans ) {
// onActivityResult is called immediately *before* onResume --
// meaning m_gi etc are still null.
m_missingDevs = data.getStringArrayExtra( InviteDelegate.DEVS ); m_missingDevs = data.getStringArrayExtra( InviteDelegate.DEVS );
m_missingCounts = data.getIntArrayExtra( InviteDelegate.COUNTS ); m_missingCounts = data.getIntArrayExtra( InviteDelegate.COUNTS );
m_missingMeans = (BT_INVITE_RESULT == requestCode) m_missingMeans = missingMeans;
? InviteMeans.BLUETOOTH : InviteMeans.SMS;
break;
} }
} }
} }
@ -729,6 +737,7 @@ public class BoardDelegate extends DelegateBase
boolean inTrade = false; boolean inTrade = false;
MenuItem item; MenuItem item;
int strId; int strId;
boolean enable;
if ( null != m_gsi ) { if ( null != m_gsi ) {
inTrade = m_gsi.inTrade; inTrade = m_gsi.inTrade;
@ -773,10 +782,10 @@ public class BoardDelegate extends DelegateBase
Utils.setItemVisible( menu, R.id.board_menu_game_resign, !inTrade ); Utils.setItemVisible( menu, R.id.board_menu_game_resign, !inTrade );
if ( !inTrade ) { if ( !inTrade ) {
boolean enabled = null == m_gsi || m_gsi.curTurnSelected; enable = null == m_gsi || m_gsi.curTurnSelected;
item = menu.findItem( R.id.board_menu_done ); item = menu.findItem( R.id.board_menu_done );
item.setVisible( enabled ); item.setVisible( enable );
if ( enabled ) { if ( enable ) {
if ( 0 >= m_view.curPending() ) { if ( 0 >= m_view.curPending() ) {
strId = R.string.board_menu_pass; strId = R.string.board_menu_pass;
} else { } else {
@ -791,7 +800,10 @@ public class BoardDelegate extends DelegateBase
} }
} }
boolean enable = null != m_gi enable = m_gameOver && rematchSupported( false );
Utils.setItemVisible( menu, R.id.board_menu_rematch, enable );
enable = null != m_gi
&& DeviceRole.SERVER_STANDALONE != m_gi.serverRole; && DeviceRole.SERVER_STANDALONE != m_gi.serverRole;
Utils.setItemVisible( menu, R.id.gamel_menu_checkmoves, enable ); Utils.setItemVisible( menu, R.id.gamel_menu_checkmoves, enable );
Utils.setItemVisible( menu, R.id.board_menu_game_resend, Utils.setItemVisible( menu, R.id.board_menu_game_resend,
@ -828,6 +840,10 @@ public class BoardDelegate extends DelegateBase
} }
break; break;
case R.id.board_menu_rematch:
doRematchIf();
break;
case R.id.board_menu_trade_commit: case R.id.board_menu_trade_commit:
cmd = JNICmd.CMD_COMMIT; cmd = JNICmd.CMD_COMMIT;
break; break;
@ -1039,11 +1055,15 @@ public class BoardDelegate extends DelegateBase
break; break;
case BLUETOOTH: case BLUETOOTH:
BTInviteDelegate.launchForResult( m_activity, m_nMissing, BTInviteDelegate.launchForResult( m_activity, m_nMissing,
BT_INVITE_RESULT ); RequestCode.BT_INVITE_RESULT );
break; break;
case SMS: case SMS:
SMSInviteDelegate.launchForResult( m_activity, m_nMissing, SMSInviteDelegate.launchForResult( m_activity, m_nMissing,
SMS_INVITE_RESULT ); RequestCode.SMS_INVITE_RESULT );
break;
case RELAY:
RelayInviteDelegate.launchForResult( m_activity, m_nMissing,
RequestCode.RELAY_INVITE_RESULT );
break; break;
case EMAIL: case EMAIL:
case CLIPBOARD: case CLIPBOARD:
@ -1661,8 +1681,8 @@ public class BoardDelegate extends DelegateBase
m_nMissing = 0; m_nMissing = 0;
post( new Runnable() { post( new Runnable() {
public void run() { public void run() {
showNotAgainDlgThen( R.string.not_again_turnchanged, showNotAgainDlg( R.string.not_again_turnchanged,
R.string.key_notagain_turnchanged ); R.string.key_notagain_turnchanged );
} }
} ); } );
m_jniThread.handle( JNICmd. CMD_ZOOM, -8 ); m_jniThread.handle( JNICmd. CMD_ZOOM, -8 );
@ -2035,6 +2055,8 @@ public class BoardDelegate extends DelegateBase
m_view.startHandling( m_activity, m_jniThread, m_jniGamePtr, m_gi, m_view.startHandling( m_activity, m_jniThread, m_jniGamePtr, m_gi,
m_connTypes ); m_connTypes );
if ( null != m_xport ) { if ( null != m_xport ) {
// informMissing should have been called by now
Assert.assertNotNull( m_connTypes );
m_xport.setReceiver( m_jniThread, m_handler ); m_xport.setReceiver( m_jniThread, m_handler );
} }
m_jniThread.handle( JNICmd.CMD_START ); m_jniThread.handle( JNICmd.CMD_START );
@ -2284,7 +2306,7 @@ public class BoardDelegate extends DelegateBase
if ( BuildConstants.CHAT_SUPPORTED ) { if ( BuildConstants.CHAT_SUPPORTED ) {
Intent intent = new Intent( m_activity, ChatActivity.class ); Intent intent = new Intent( m_activity, ChatActivity.class );
intent.putExtra( GameUtils.INTENT_KEY_ROWID, m_rowid ); intent.putExtra( GameUtils.INTENT_KEY_ROWID, m_rowid );
startActivityForResult( intent, CHAT_REQUEST ); startActivityForResult( intent, RequestCode.CHAT_REQUEST );
} }
} }
@ -2348,50 +2370,56 @@ public class BoardDelegate extends DelegateBase
private void tryInvites() private void tryInvites()
{ {
if ( XWApp.BTSUPPORTED || XWApp.SMSSUPPORTED ) { if ( 0 < m_nMissing && m_summary.hasRematchInfo() ) {
// test if summary knows of rematch pending first tryRematchInvites();
if ( 0 < m_nMissing && m_summary.hasRematchInfo() ) { } else if ( null != m_missingDevs ) {
tryRematchInvites(); Assert.assertNotNull( m_missingMeans );
} else if ( null != m_missingDevs ) { String gameName = GameUtils.getName( m_activity, m_rowid );
Assert.assertNotNull( m_missingMeans ); m_invitesPending = m_missingDevs.length;
String gameName = GameUtils.getName( m_activity, m_rowid ); for ( int ii = 0; ii < m_missingDevs.length; ++ii ) {
m_invitesPending = m_missingDevs.length; String dev = m_missingDevs[ii];
for ( int ii = 0; ii < m_missingDevs.length; ++ii ) { int nPlayers = m_missingCounts[ii];
String dev = m_missingDevs[ii]; Assert.assertTrue( 0 <= m_nGuestDevs );
int nPlayers = m_missingCounts[ii]; int forceChannel = ii + m_nGuestDevs + 1;
Assert.assertTrue( 0 <= m_nGuestDevs ); NetLaunchInfo nli = new NetLaunchInfo( m_summary, m_gi,
int forceChannel = ii + m_nGuestDevs + 1; nPlayers, forceChannel );
NetLaunchInfo nli = new NetLaunchInfo( m_summary, m_gi, if ( !m_relayConnected ) {
nPlayers, forceChannel ); nli.removeAddress( CommsConnType.COMMS_CONN_RELAY );
if ( !m_relayConnected ) { }
nli.removeAddress( CommsConnType.COMMS_CONN_RELAY );
} switch ( m_missingMeans ) {
case BLUETOOTH:
switch ( m_missingMeans ) { if ( ! m_progressShown ) {
case BLUETOOTH: m_progressShown = true;
if ( ! m_progressShown ) { String progMsg = BTService.nameForAddr( dev );
m_progressShown = true; progMsg = getString( R.string.invite_progress_fmt, progMsg );
String progMsg = BTService.nameForAddr( dev ); startProgress( R.string.invite_progress_title, progMsg,
progMsg = getString( R.string.invite_progress_fmt, progMsg ); new OnCancelListener() {
startProgress( R.string.invite_progress_title, progMsg, public void onCancel( DialogInterface dlg )
new OnCancelListener() { {
public void onCancel( DialogInterface dlg ) m_progressShown = false;
{ }
m_progressShown = false; });
} }
}); BTService.inviteRemote( m_activity, dev, nli );
} break;
BTService.inviteRemote( m_activity, dev, nli ); case SMS:
break; SMSService.inviteRemote( m_activity, dev, nli );
case SMS: break;
SMSService.inviteRemote( m_activity, dev, nli ); case RELAY:
break; try {
} int destDevID = Integer.parseInt( dev ); // failing
RelayService.inviteRemote( m_activity, destDevID,
null, nli );
} catch (NumberFormatException nfi) {
DbgUtils.loge( nfi );
}
break;
} }
m_missingDevs = null;
m_missingCounts = null;
m_missingMeans = null;
} }
m_missingDevs = null;
m_missingCounts = null;
m_missingMeans = null;
} }
} }
@ -2497,29 +2525,77 @@ public class BoardDelegate extends DelegateBase
} }
// For now, supported if standalone or either BT or SMS used for transport // For now, supported if standalone or either BT or SMS used for transport
private boolean rematchSupported() private boolean rematchSupported( boolean showMulti )
{
return rematchSupported( showMulti ? m_activity : null,
m_summary );
}
public static boolean rematchSupported( Context context, long rowID )
{
GameSummary summary = DBUtils.getSummary( context, rowID, 1 );
return null != summary && rematchSupported( null, summary );
}
private static boolean rematchSupported( Context context,
GameSummary summary )
{ {
boolean supported = false; boolean supported = false;
if ( XWApp.REMATCH_SUPPORTED ) { if ( XWApp.REMATCH_SUPPORTED ) {
supported = m_gi.serverRole == DeviceRole.SERVER_STANDALONE; // standalone games are easy to rematch
if ( !supported && 2 == m_gi.nPlayers ) { supported = summary.serverRole == DeviceRole.SERVER_STANDALONE;
supported = m_connTypes.contains( CommsConnType.COMMS_CONN_BT )
|| m_connTypes.contains( CommsConnType.COMMS_CONN_SMS ) if ( !supported ) {
|| m_connTypes.contains( CommsConnType.COMMS_CONN_RELAY ); if ( 2 == summary.nPlayers ) {
if ( !summary.anyMissing() ) {
CommsConnTypeSet connTypes = summary.conTypes;
supported = connTypes.contains( CommsConnType.COMMS_CONN_BT )
|| connTypes.contains( CommsConnType.COMMS_CONN_SMS )
|| connTypes.contains( CommsConnType.COMMS_CONN_RELAY );
}
} else if ( null != context ) {
// show the button if people haven't dismissed the hint yet
supported = ! XWPrefs
.getPrefsBoolean( context,
R.string.key_na_rematch_two_only,
false );
}
} }
} }
return supported; return supported;
} }
private void doRematch() private void doRematchIf()
{ {
if ( doRematchIf( m_activity, this, m_rowid, m_summary, m_gi,
m_jniGamePtr ) ) {
finish();
}
}
private static boolean doRematchIf( Activity activity, DelegateBase dlgt,
long rowid, GameSummary summary,
CurGameInfo gi, int jniGamePtr )
{
boolean success = false;
if ( XWApp.REMATCH_SUPPORTED ) { if ( XWApp.REMATCH_SUPPORTED ) {
boolean doIt = true;
String phone = null; String phone = null;
String btAddr = null; String btAddr = null;
String relayID = null; String relayID = null;
if ( m_gi.serverRole != DeviceRole.SERVER_STANDALONE ) { if ( DeviceRole.SERVER_STANDALONE == gi.serverRole ) {
CommsAddrRec[] addrs = XwJNI.comms_getAddrs( m_jniGamePtr ); // nothing to do??
for ( CommsAddrRec addr : addrs ) { } else if ( 2 != gi.nPlayers ) {
Assert.assertNotNull( dlgt );
if ( null != dlgt ) {
dlgt.showNotAgainDlg( R.string.not_again_rematch_two_only,
R.string.key_na_rematch_two_only );
}
doIt = false;
} else {
CommsAddrRec[] addrs = XwJNI.comms_getAddrs( jniGamePtr );
for ( int ii = 0; ii < addrs.length; ++ii ) {
CommsAddrRec addr = addrs[ii];
if ( addr.contains( CommsConnType.COMMS_CONN_BT ) ) { if ( addr.contains( CommsConnType.COMMS_CONN_BT ) ) {
Assert.assertNull( btAddr ); Assert.assertNull( btAddr );
btAddr = addr.bt_btAddr; btAddr = addr.bt_btAddr;
@ -2530,19 +2606,42 @@ public class BoardDelegate extends DelegateBase
} }
if ( addr.contains( CommsConnType.COMMS_CONN_RELAY ) ) { if ( addr.contains( CommsConnType.COMMS_CONN_RELAY ) ) {
Assert.assertNull( relayID ); Assert.assertNull( relayID );
relayID = m_summary.relayID; relayID = XwJNI.comms_formatRelayID( jniGamePtr, ii );
} }
} }
} }
Intent intent = GamesListDelegate if ( doIt ) {
.makeRematchIntent( m_activity, m_rowid, m_connTypes, btAddr, CommsConnTypeSet connTypes = summary.conTypes;
phone, relayID ); String newName = summary.getRematchName();
if ( null != intent ) { Intent intent = GamesListDelegate
startActivity( intent ); .makeRematchIntent( activity, rowid, gi.dictName,
finish(); gi.dictLang, connTypes, btAddr,
phone, relayID, newName );
if ( null != intent ) {
activity.startActivity( intent );
success = true;
}
} }
} }
return success;
}
public static void setupRematchFor( Activity activity, long rowID )
{
GameLock lock = new GameLock( rowID, false );
if ( lock.tryLock() ) {
GameSummary summary = DBUtils.getSummary( activity, lock );
CurGameInfo gi = new CurGameInfo( activity );
int gamePtr = GameUtils.loadMakeGame( activity, gi, lock );
doRematchIf( activity, null, rowID, summary, gi, gamePtr );
XwJNI.game_dispose( gamePtr );
lock.unlock();
} else {
DbgUtils.logf( "setupRematchFor(): unable to lock game" );
}
} }
private void tryRematchInvites() private void tryRematchInvites()
@ -2567,10 +2666,9 @@ public class BoardDelegate extends DelegateBase
if ( null != value ) { if ( null != value ) {
BTService.inviteRemote( m_activity, value, nli ); BTService.inviteRemote( m_activity, value, nli );
} }
value = m_summary.getStringExtra( GameSummary.EXTRA_REMATCH_RELAY ); value = m_summary.getStringExtra( GameSummary.EXTRA_REMATCH_RELAY );
if ( null != value ) { if ( null != value ) {
RelayService.inviteRemote( m_activity, value, nli ); RelayService.inviteRemote( m_activity, 0, value, nli );
} }
} }
} }

View file

@ -46,6 +46,7 @@ public class CommsTransport implements TransportProcs,
private SocketChannel m_socketChannel; private SocketChannel m_socketChannel;
private int m_jniGamePtr; private int m_jniGamePtr;
private CommsAddrRec m_relayAddr; private CommsAddrRec m_relayAddr;
private String m_useHost;
private JNIThread m_jniThread; private JNIThread m_jniThread;
private CommsThread m_thread; private CommsThread m_thread;
private TransportProcs.TPMsgHandler m_tpHandler; private TransportProcs.TPMsgHandler m_tpHandler;
@ -86,7 +87,7 @@ public class CommsTransport implements TransportProcs,
m_done = false; m_done = false;
boolean failed = true; boolean failed = true;
try { try {
if ( Build.PRODUCT.contains("sdk") ) { if ( XWApp.onEmulator() ) {
System.setProperty("java.net.preferIPv6Addresses", "false"); System.setProperty("java.net.preferIPv6Addresses", "false");
} }
@ -99,8 +100,7 @@ public class CommsTransport implements TransportProcs,
DbgUtils.loge( ioe ); DbgUtils.loge( ioe );
} catch ( UnresolvedAddressException uae ) { } catch ( UnresolvedAddressException uae ) {
DbgUtils.logf( "bad address: name: %s; port: %s; exception: %s", DbgUtils.logf( "bad address: name: %s; port: %s; exception: %s",
m_relayAddr.ip_relay_hostName, m_useHost, m_relayAddr.ip_relay_port,
m_relayAddr.ip_relay_port,
uae.toString() ); uae.toString() );
} }
@ -128,10 +128,10 @@ public class CommsTransport implements TransportProcs,
m_socketChannel = SocketChannel.open(); m_socketChannel = SocketChannel.open();
m_socketChannel.configureBlocking( false ); m_socketChannel.configureBlocking( false );
DbgUtils.logf( "connecting to %s:%d", DbgUtils.logf( "connecting to %s:%d",
m_relayAddr.ip_relay_hostName, m_useHost,
m_relayAddr.ip_relay_port ); m_relayAddr.ip_relay_port );
InetSocketAddress isa = new InetSocketAddress isa = new
InetSocketAddress(m_relayAddr.ip_relay_hostName, InetSocketAddress(m_useHost,
m_relayAddr.ip_relay_port ); m_relayAddr.ip_relay_port );
m_socketChannel.connect( isa ); m_socketChannel.connect( isa );
} catch ( java.io.IOException ioe ) { } catch ( java.io.IOException ioe ) {
@ -356,9 +356,11 @@ public class CommsTransport implements TransportProcs,
public int getFlags() { return COMMS_XPORT_FLAGS_NONE; } public int getFlags() { return COMMS_XPORT_FLAGS_NONE; }
public int transportSend( byte[] buf, CommsAddrRec addr, public int transportSend( byte[] buf, String msgNo, CommsAddrRec addr,
CommsConnType conType, int gameID ) CommsConnType conType, int gameID )
{ {
DbgUtils.logdf( "CommsTransport.transportSend(len=%d, typ=%s)",
buf.length, conType.toString() );
int nSent = -1; int nSent = -1;
Assert.assertNotNull( addr ); Assert.assertNotNull( addr );
Assert.assertTrue( addr.contains( conType ) ); Assert.assertTrue( addr.contains( conType ) );
@ -366,6 +368,7 @@ public class CommsTransport implements TransportProcs,
if ( !XWApp.UDP_ENABLED && conType == CommsConnType.COMMS_CONN_RELAY if ( !XWApp.UDP_ENABLED && conType == CommsConnType.COMMS_CONN_RELAY
&& null == m_relayAddr ) { && null == m_relayAddr ) {
m_relayAddr = new CommsAddrRec( addr ); m_relayAddr = new CommsAddrRec( addr );
m_useHost = NetUtils.forceHost( m_relayAddr.ip_relay_hostName );
} }
if ( !XWApp.UDP_ENABLED && conType == CommsConnType.COMMS_CONN_RELAY ) { if ( !XWApp.UDP_ENABLED && conType == CommsConnType.COMMS_CONN_RELAY ) {
@ -385,7 +388,7 @@ public class CommsTransport implements TransportProcs,
// Keep this while debugging why the resend_all that gets // Keep this while debugging why the resend_all that gets
// fired on reconnect doesn't unstall a game but a manual // fired on reconnect doesn't unstall a game but a manual
// resend does. // resend does.
DbgUtils.logf( "transportSend(%d)=>%d", buf.length, nSent ); DbgUtils.logdf( "transportSend(%d)=>%d", buf.length, nSent );
return nSent; return nSent;
} }
@ -426,7 +429,7 @@ public class CommsTransport implements TransportProcs,
m_tpHandler.tpmRelayErrorProc( relayErr ); m_tpHandler.tpmRelayErrorProc( relayErr );
} }
public boolean relayNoConnProc( byte[] buf, String relayID ) public boolean relayNoConnProc( byte[] buf, String msgNo, String relayID )
{ {
Assert.fail(); Assert.fail();
return false; return false;
@ -447,7 +450,7 @@ public class CommsTransport implements TransportProcs,
gameID, buf ); gameID, buf );
break; break;
case COMMS_CONN_BT: case COMMS_CONN_BT:
nSent = BTService.enqueueFor( context, buf, addr.bt_btAddr, gameID ); nSent = BTService.enqueueFor( context, buf, addr, gameID );
break; break;
default: default:
Assert.fail(); Assert.fail();

View file

@ -190,8 +190,14 @@ public class ConnStatusHandler {
R.string.connstat_net_fmt, R.string.connstat_net_fmt,
connTypes.toString( context ))); connTypes.toString( context )));
for ( CommsConnType typ : connTypes.getTypes() ) { for ( CommsConnType typ : connTypes.getTypes() ) {
sb.append( String.format( "\n\n*** %s ***\n", String did = "";
typ.longName( context ) ) ); if ( BuildConfig.DEBUG
&& CommsConnType.COMMS_CONN_RELAY == typ ) {
did = String.format( "(DevID: %d) ",
DevID.getRelayDevIDInt(context) );
}
sb.append( String.format( "\n\n*** %s %s***\n",
typ.longName( context ), did ) );
SuccessRecord record = recordFor( typ, false ); SuccessRecord record = recordFor( typ, false );
tmp = LocUtils.getString( context, record.successNewer? tmp = LocUtils.getString( context, record.successNewer?
R.string.connstat_succ : R.string.connstat_succ :

View file

@ -109,7 +109,7 @@ public class DBHelper extends SQLiteOpenHelper {
private static final String[][] s_summaryColsAndTypes = { private static final String[][] s_summaryColsAndTypes = {
{ "rowid", "INTEGER PRIMARY KEY AUTOINCREMENT" } { "rowid", "INTEGER PRIMARY KEY AUTOINCREMENT" }
,{ VISID, "INTEGER" } ,{ VISID, "INTEGER" } // user-visible ID
,{ GAME_NAME, "TEXT" } ,{ GAME_NAME, "TEXT" }
,{ NUM_MOVES, "INTEGER" } ,{ NUM_MOVES, "INTEGER" }
,{ TURN, "INTEGER" } ,{ TURN, "INTEGER" }

View file

@ -1025,10 +1025,18 @@ public class DBUtils {
public long m_rowid; public long m_rowid;
public long m_nextNag; public long m_nextNag;
public long m_lastMoveMillis; public long m_lastMoveMillis;
public NeedsNagInfo( long rowid, long nextNag, long lastMove ) { private boolean m_isSolo;
public NeedsNagInfo( long rowid, long nextNag, long lastMove,
CurGameInfo.DeviceRole role ) {
m_rowid = rowid; m_rowid = rowid;
m_nextNag = nextNag; m_nextNag = nextNag;
m_lastMoveMillis = 1000 * lastMove; m_lastMoveMillis = 1000 * lastMove;
m_isSolo = CurGameInfo.DeviceRole.SERVER_STANDALONE == role;
}
public boolean isSolo() {
return m_isSolo;
} }
} }
@ -1036,7 +1044,8 @@ public class DBUtils {
{ {
NeedsNagInfo[] result = null; NeedsNagInfo[] result = null;
long now = new Date().getTime(); // in milliseconds long now = new Date().getTime(); // in milliseconds
String[] columns = { ROW_ID, DBHelper.NEXTNAG, DBHelper.LASTMOVE }; String[] columns = { ROW_ID, DBHelper.NEXTNAG, DBHelper.LASTMOVE,
DBHelper.SERVERROLE };
// where nextnag > 0 AND nextnag < now // where nextnag > 0 AND nextnag < now
String selection = String selection =
String.format( "%s > 0 AND %s < %s", DBHelper.NEXTNAG, String.format( "%s > 0 AND %s < %s", DBHelper.NEXTNAG,
@ -1052,11 +1061,14 @@ public class DBUtils {
int rowIndex = cursor.getColumnIndex(ROW_ID); int rowIndex = cursor.getColumnIndex(ROW_ID);
int nagIndex = cursor.getColumnIndex( DBHelper.NEXTNAG ); int nagIndex = cursor.getColumnIndex( DBHelper.NEXTNAG );
int lastMoveIndex = cursor.getColumnIndex( DBHelper.LASTMOVE ); int lastMoveIndex = cursor.getColumnIndex( DBHelper.LASTMOVE );
int roleIndex = cursor.getColumnIndex( DBHelper.SERVERROLE );
for ( int ii = 0; ii < result.length && cursor.moveToNext(); ++ii ) { for ( int ii = 0; ii < result.length && cursor.moveToNext(); ++ii ) {
long rowid = cursor.getLong( rowIndex ); long rowid = cursor.getLong( rowIndex );
long nextNag = cursor.getLong( nagIndex ); long nextNag = cursor.getLong( nagIndex );
long lastMove = cursor.getLong( lastMoveIndex ); long lastMove = cursor.getLong( lastMoveIndex );
result[ii] = new NeedsNagInfo( rowid, nextNag, lastMove ); CurGameInfo.DeviceRole role =
CurGameInfo.DeviceRole.values()[cursor.getInt( roleIndex )];
result[ii] = new NeedsNagInfo( rowid, nextNag, lastMove, role );
} }
} }
@ -1762,10 +1774,15 @@ public class DBUtils {
public static boolean gameDBExists( Context context ) public static boolean gameDBExists( Context context )
{ {
String name = DBHelper.getDBName(); String varName = getVariantDBName();
File sdcardDB = new File( Environment.getExternalStorageDirectory(), boolean exists = new File( Environment.getExternalStorageDirectory(),
name ); varName ).exists();
return sdcardDB.exists(); if ( !exists ) {
// try the old one
exists = new File( Environment.getExternalStorageDirectory(),
DBHelper.getDBName() ).exists();
}
return exists;
} }
public static String[] getColumns( SQLiteDatabase db, String name ) public static String[] getColumns( SQLiteDatabase db, String name )
@ -2044,20 +2061,39 @@ public class DBUtils {
return dflt; return dflt;
} }
// public static void setIntFor( Context context, String key, int value ) public static void setIntFor( Context context, String key, int value )
// { {
// String asStr = String.format( "%d", value ); DbgUtils.logdf( "DBUtils.setIntFor(key=%s, val=%d)", key, value );
// setStringFor( context, key, asStr ); String asStr = String.format( "%d", value );
// } setStringFor( context, key, asStr );
}
// public static int getIntFor( Context context, String key, int dflt ) public static int getIntFor( Context context, String key, int dflt )
// { {
// String asStr = getStringFor( context, key, null ); String asStr = getStringFor( context, key, null );
// if ( null != asStr ) { if ( null != asStr ) {
// dflt = Integer.parseInt( asStr ); dflt = Integer.parseInt( asStr );
// } }
// return dflt; DbgUtils.logdf( "DBUtils.getIntFor(key=%s)=>%d", key, dflt );
// } return dflt;
}
public static void setBoolFor( Context context, String key, boolean value )
{
// DbgUtils.logdf( "DBUtils.setBoolFor(key=%s, val=%b)", key, value );
String asStr = String.format( "%b", value );
setStringFor( context, key, asStr );
}
public static boolean getBoolFor( Context context, String key, boolean dflt )
{
String asStr = getStringFor( context, key, null );
if ( null != asStr ) {
dflt = Boolean.parseBoolean( asStr );
}
DbgUtils.logdf( "DBUtils.getBoolFor(key=%s)=>%b", key, dflt );
return dflt;
}
public static int getIncrementIntFor( Context context, String key, int dflt, public static int getIncrementIntFor( Context context, String key, int dflt,
final int incr ) final int incr )
@ -2097,8 +2133,16 @@ public class DBUtils {
{ {
String name = DBHelper.getDBName(); String name = DBHelper.getDBName();
File gamesDB = context.getDatabasePath( name ); File gamesDB = context.getDatabasePath( name );
// Use the variant name EXCEPT where we're copying from sdCard and
// only the older name exists.
File sdcardDB = new File( Environment.getExternalStorageDirectory(), File sdcardDB = new File( Environment.getExternalStorageDirectory(),
name ); getVariantDBName() );
if ( !toSDCard && !sdcardDB.exists() ) {
sdcardDB = new File( Environment.getExternalStorageDirectory(),
name );
}
try { try {
File srcDB = toSDCard? gamesDB : sdcardDB; File srcDB = toSDCard? gamesDB : sdcardDB;
if ( srcDB.exists() ) { if ( srcDB.exists() ) {
@ -2113,6 +2157,12 @@ public class DBUtils {
} }
} }
private static String getVariantDBName()
{
return String.format( "%s_%s", DBHelper.getDBName(),
BuildConstants.VARIANT );
}
// Chat is independent of the GameLock mechanism because it's not // Chat is independent of the GameLock mechanism because it's not
// touching the SNAPSHOT column. // touching the SNAPSHOT column.
private static void saveChatHistory( Context context, long rowid, private static void saveChatHistory( Context context, long rowid,

View file

@ -111,13 +111,10 @@ public class DbgUtils {
public static void printStack( StackTraceElement[] trace ) public static void printStack( StackTraceElement[] trace )
{ {
if ( s_doLog ) { if ( s_doLog && null != trace ) {
if ( null == trace ) { // 2: skip printStack etc.
DbgUtils.logf( "printStack(): null trace" ); for ( int ii = 2; ii < trace.length; ++ii ) {
} else { DbgUtils.logf( "ste %d: %s", ii, trace[ii].toString() );
for ( int ii = 0; ii < trace.length; ++ii ) {
DbgUtils.logf( "ste %d: %s", ii, trace[ii].toString() );
}
} }
} }
} }

View file

@ -19,6 +19,8 @@
package org.eehouse.android.xw4; package org.eehouse.android.xw4;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
import android.app.Activity; import android.app.Activity;
import android.app.AlertDialog; import android.app.AlertDialog;
import android.app.Dialog; import android.app.Dialog;
@ -76,12 +78,15 @@ public class DelegateBase implements DlgClickNotify,
protected void onSaveInstanceState( Bundle outState ) {} protected void onSaveInstanceState( Bundle outState ) {}
public boolean onPrepareOptionsMenu( Menu menu ) { return false; } public boolean onPrepareOptionsMenu( Menu menu ) { return false; }
public boolean onOptionsItemSelected( MenuItem item ) { return false; } public boolean onOptionsItemSelected( MenuItem item ) { return false; }
public void onCreateContextMenu( ContextMenu menu, View view,
ContextMenuInfo menuInfo ) {}
public boolean onContextItemSelected( MenuItem item ) { return false; }
protected void onStart() {} protected void onStart() {}
protected void onStop() {} protected void onStop() {}
protected void onDestroy() {} protected void onDestroy() {}
protected void onWindowFocusChanged( boolean hasFocus ) {} protected void onWindowFocusChanged( boolean hasFocus ) {}
protected boolean onBackPressed() { return false; } protected boolean onBackPressed() { return false; }
protected void onActivityResult( int requestCode, int resultCode, protected void onActivityResult( RequestCode requestCode, int resultCode,
Intent data ) Intent data )
{ {
DbgUtils.logf( "DelegateBase.onActivityResult(): subclass responsibility!!!" ); DbgUtils.logf( "DelegateBase.onActivityResult(): subclass responsibility!!!" );
@ -174,9 +179,10 @@ public class DelegateBase implements DlgClickNotify,
return m_activity.getTitle().toString(); return m_activity.getTitle().toString();
} }
protected void startActivityForResult( Intent intent, int requestCode ) protected void startActivityForResult( Intent intent,
RequestCode requestCode )
{ {
m_activity.startActivityForResult( intent, requestCode ); m_activity.startActivityForResult( intent, requestCode.ordinal() );
} }
protected void setResult( int result, Intent intent ) protected void setResult( int result, Intent intent )
@ -431,6 +437,13 @@ public class DelegateBase implements DlgClickNotify,
m_dlgDelegate.showConfirmThen( msgID, action ); m_dlgDelegate.showConfirmThen( msgID, action );
} }
public void showConfirmThen( Runnable onNA, String msg, int posButton,
int negButton, Action action, Object... params )
{
m_dlgDelegate.showConfirmThen( onNA, msg, posButton, negButton, action,
params );
}
protected boolean post( Runnable runnable ) protected boolean post( Runnable runnable )
{ {
return m_dlgDelegate.post( runnable ); return m_dlgDelegate.post( runnable );

View file

@ -21,9 +21,16 @@ package org.eehouse.android.xw4;
import android.app.Activity; import android.app.Activity;
import android.os.Bundle; import android.os.Bundle;
import android.widget.ListAdapter;
import android.widget.ListView;
public interface Delegator { public interface Delegator {
Activity getActivity(); Activity getActivity();
Bundle getArguments(); Bundle getArguments();
void finish(); void finish();
// For activities with lists
void setListAdapter( ListAdapter adapter );
ListAdapter getListAdapter();
ListView getListView();
} }

View file

@ -0,0 +1,149 @@
/* -*- compile-command: "find-and-ant.sh debug install"; -*- */
/*
* Copyright 2010 - 2015 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;
/* The relay issues an identifier for a registered device. It's a string
* representation of a 32-bit hex number. When devices register, they pass
* what's meant to be a unique identifier of their own. GCM-aware devices (for
* which this was originally conceived) pass their GCM IDs (which can change,
* and require re-registration). Other devices generate an ID however they
* choose, or can pass "", meaning "I'm anonymous; just give me an ID based on
* nothing."
*/
import android.content.Context;
import com.google.android.gcm.GCMRegistrar;
import junit.framework.Assert;
public class DevID {
private static final String DEVID_KEY = "DevID.devid_key";
private static final String DEVID_ACK_KEY = "key_relay_regid_ackd";
private static final String GCM_REGVERS_KEY = "key_gcmvers_regid";
private static String s_relayDevID;
private static int s_asInt;
// Called, likely on DEBUG builds only, when the relay hostname is
// changed. DevIDs are invalid at that point.
public static void hostChanged( Context context )
{
clearRelayDevID( context );
RelayService.reset( context );
}
public static int getRelayDevIDInt( Context context )
{
if ( 0 == s_asInt ) {
String asStr = getRelayDevID( context );
if ( null != asStr && 0 < asStr.length() ) {
s_asInt = Integer.valueOf( asStr, 16 );
}
}
return s_asInt;
}
public static String getRelayDevID( Context context, boolean insistAckd )
{
String result = getRelayDevID( context );
if ( insistAckd && null != result && 0 < result.length()
&& ! DBUtils.getBoolFor( context, DEVID_ACK_KEY, false ) ) {
result = null;
}
return result;
}
public static String getRelayDevID( Context context )
{
if ( null == s_relayDevID ) {
String asStr = DBUtils.getStringFor( context, DEVID_KEY, "" );
// TRANSITIONAL: If it's not there, see if it's stored the old way
if ( 0 == asStr.length() ) {
asStr = XWPrefs.getPrefsString( context, R.string.key_relay_regid );
}
if ( null != asStr && 0 != asStr.length() ) {
s_relayDevID = asStr;
}
}
DbgUtils.logdf( "DevID.getRelayDevID() => %s", s_relayDevID );
return s_relayDevID;
}
public static void setRelayDevID( Context context, String devID )
{
DbgUtils.logdf( "DevID.setRelayDevID()" );
if ( BuildConfig.DEBUG ) {
String oldID = getRelayDevID( context );
if ( null != oldID && 0 < oldID.length()
&& ! devID.equals( oldID ) ) {
DbgUtils.logdf( "devID changing!!!: %s => %s", oldID, devID );
}
}
DBUtils.setStringFor( context, DEVID_KEY, devID );
s_relayDevID = devID;
DBUtils.setBoolFor( context, DEVID_ACK_KEY, true );
// DbgUtils.printStack();
}
public static void clearRelayDevID( Context context )
{
DbgUtils.logf( "DevID.clearRelayDevID()" );
DBUtils.setStringFor( context, DEVID_KEY, "" );
// DbgUtils.printStack();
}
public static void setGCMDevID( Context context, String devID )
{
int curVers = Utils.getAppVersion( context );
DBUtils.setIntFor( context, GCM_REGVERS_KEY, curVers );
DBUtils.setBoolFor( context, DEVID_ACK_KEY, false );
}
public static String getGCMDevID( Context context )
{
int curVers = Utils.getAppVersion( context );
int storedVers = DBUtils.getIntFor( context, GCM_REGVERS_KEY, 0 );
// TRANSITIONAL
if ( 0 == storedVers ) {
storedVers = XWPrefs.getPrefsInt( context,
R.string.key_gcmvers_regid, 0 );
if ( 0 != storedVers ) {
DBUtils.setIntFor( context, GCM_REGVERS_KEY, storedVers );
}
}
String result;
if ( 0 != storedVers && storedVers < curVers ) {
result = ""; // Don't trust what registrar has
} else {
result = GCMRegistrar.getRegistrationId( context );
}
return result;
}
public static void clearGCMDevID( Context context )
{
DBUtils.setBoolFor( context, DEVID_ACK_KEY, false );
}
}

View file

@ -22,7 +22,7 @@ package org.eehouse.android.xw4;
import android.os.Bundle; import android.os.Bundle;
public class DictBrowseActivity extends XWListActivity { public class DictBrowseActivity extends XWActivity {
private DictBrowseDelegate m_dlgt; private DictBrowseDelegate m_dlgt;

View file

@ -21,7 +21,6 @@
package org.eehouse.android.xw4; package org.eehouse.android.xw4;
import android.app.Activity; import android.app.Activity;
import android.app.ListActivity;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.database.DataSetObserver; import android.database.DataSetObserver;
@ -156,7 +155,7 @@ public class DictBrowseDelegate extends ListDelegateBase
} }
} }
protected DictBrowseDelegate( ListDelegator delegator, Bundle savedInstanceState ) protected DictBrowseDelegate( Delegator delegator, Bundle savedInstanceState )
{ {
super( delegator, savedInstanceState, R.layout.dict_browser ); super( delegator, savedInstanceState, R.layout.dict_browser );
m_activity = delegator.getActivity(); m_activity = delegator.getActivity();

View file

@ -33,7 +33,7 @@ import java.util.HashMap;
import org.eehouse.android.xw4.DictUtils.DictAndLoc; import org.eehouse.android.xw4.DictUtils.DictAndLoc;
import org.eehouse.android.xw4.loc.LocUtils; import org.eehouse.android.xw4.loc.LocUtils;
public class DictsActivity extends XWListActivity { public class DictsActivity extends XWActivity {
private static interface SafePopup { private static interface SafePopup {
public void doPopup( Context context, View button, public void doPopup( Context context, View button,

View file

@ -23,7 +23,6 @@ package org.eehouse.android.xw4;
import android.app.Activity; import android.app.Activity;
import android.app.AlertDialog; import android.app.AlertDialog;
import android.app.Dialog; import android.app.Dialog;
import android.app.ListActivity;
import android.content.Context; import android.content.Context;
import android.content.DialogInterface.OnCancelListener; import android.content.DialogInterface.OnCancelListener;
import android.content.DialogInterface.OnClickListener; import android.content.DialogInterface.OnClickListener;
@ -47,7 +46,7 @@ import android.widget.LinearLayout;
import android.widget.ListView; import android.widget.ListView;
import android.widget.TextView; import android.widget.TextView;
import org.apache.http.client.methods.HttpPost; import java.net.HttpURLConnection;
import org.json.JSONArray; import org.json.JSONArray;
import org.json.JSONException; import org.json.JSONException;
import org.json.JSONObject; import org.json.JSONObject;
@ -300,7 +299,7 @@ public class DictsDelegate extends ListDelegateBase
} }
} }
protected DictsDelegate( ListDelegator delegator, Bundle savedInstanceState ) protected DictsDelegate( Delegator delegator, Bundle savedInstanceState )
{ {
super( delegator, savedInstanceState, R.layout.dict_browse, super( delegator, savedInstanceState, R.layout.dict_browse,
R.menu.dicts_menu ); R.menu.dicts_menu );
@ -985,7 +984,7 @@ public class DictsDelegate extends ListDelegateBase
// return mkDownloadIntent( context, dict_url ); // return mkDownloadIntent( context, dict_url );
} }
public static void downloadForResult( Activity activity, int requestCode, public static void downloadForResult( Activity activity, RequestCode requestCode,
int lang, String name ) int lang, String name )
{ {
Intent intent = new Intent( activity, DictsActivity.class ); Intent intent = new Intent( activity, DictsActivity.class );
@ -998,16 +997,16 @@ public class DictsDelegate extends ListDelegateBase
intent.putExtra( DICT_NAME_EXTRA, name ); intent.putExtra( DICT_NAME_EXTRA, name );
} }
activity.startActivityForResult( intent, requestCode ); activity.startActivityForResult( intent, requestCode.ordinal() );
} }
public static void downloadForResult( Activity activity, int requestCode, public static void downloadForResult( Activity activity, RequestCode requestCode,
int lang ) int lang )
{ {
downloadForResult( activity, requestCode, lang, null ); downloadForResult( activity, requestCode, lang, null );
} }
public static void downloadForResult( Activity activity, int requestCode ) public static void downloadForResult( Activity activity, RequestCode requestCode )
{ {
downloadForResult( activity, requestCode, 0, null ); downloadForResult( activity, requestCode, 0, null );
} }
@ -1135,11 +1134,11 @@ public class DictsDelegate extends ListDelegateBase
// parse less data // parse less data
String name = null; String name = null;
String proc = String.format( "listDicts?lc=%s", m_lc ); String proc = String.format( "listDicts?lc=%s", m_lc );
HttpPost post = NetUtils.makePost( m_context, proc ); HttpURLConnection conn = NetUtils.makeHttpConn( m_context, proc );
if ( null != post ) { if ( null != conn ) {
JSONObject theOne = null; JSONObject theOne = null;
String langName = null; String langName = null;
String json = NetUtils.runPost( post, new JSONObject() ); String json = NetUtils.runConn( conn, new JSONObject() );
if ( null != json ) { if ( null != json ) {
try { try {
JSONObject obj = new JSONObject( json ); JSONObject obj = new JSONObject( json );
@ -1217,9 +1216,9 @@ public class DictsDelegate extends ListDelegateBase
public Boolean doInBackground( Void... unused ) public Boolean doInBackground( Void... unused )
{ {
boolean success = false; boolean success = false;
HttpPost post = NetUtils.makePost( m_context, "listDicts" ); HttpURLConnection conn = NetUtils.makeHttpConn( m_context, "listDicts" );
if ( null != post ) { if ( null != conn ) {
String json = NetUtils.runPost( post, new JSONObject() ); String json = NetUtils.runConn( conn, new JSONObject() );
if ( !isCancelled() ) { if ( !isCancelled() ) {
if ( null != json ) { if ( null != json ) {
post( new Runnable() { post( new Runnable() {

View file

@ -65,6 +65,7 @@ public class DlgDelegate {
NEW_GAME_PRESSED, NEW_GAME_PRESSED,
SET_HIDE_NEWGAME_BUTTONS, SET_HIDE_NEWGAME_BUTTONS,
DWNLD_LOC_DICT, DWNLD_LOC_DICT,
NEW_GAME_DFLT_NAME,
// BoardDelegate // BoardDelegate
UNDO_LAST_ACTION, UNDO_LAST_ACTION,
@ -138,7 +139,7 @@ public class DlgDelegate {
public interface DlgClickNotify { public interface DlgClickNotify {
public static enum InviteMeans { public static enum InviteMeans {
SMS, EMAIL, NFC, BLUETOOTH, CLIPBOARD, SMS, EMAIL, NFC, BLUETOOTH, CLIPBOARD, RELAY,
}; };
void dlgButtonClicked( Action action, int button, Object[] params ); void dlgButtonClicked( Action action, int button, Object[] params );
void inviteChoiceMade( Action action, InviteMeans means, Object[] params ); void inviteChoiceMade( Action action, InviteMeans means, Object[] params );
@ -561,7 +562,7 @@ public class DlgDelegate {
private Dialog createNotAgainDialog( final DlgState state, DlgID dlgID ) private Dialog createNotAgainDialog( final DlgState state, DlgID dlgID )
{ {
NotAgainView naView = (NotAgainView) final NotAgainView naView = (NotAgainView)
LocUtils.inflate( m_activity, R.layout.not_again_view ); LocUtils.inflate( m_activity, R.layout.not_again_view );
naView.setMessage( state.m_msg ); naView.setMessage( state.m_msg );
final OnClickListener lstnr_p = mkCallbackClickListener( state, naView ); final OnClickListener lstnr_p = mkCallbackClickListener( state, naView );
@ -571,18 +572,15 @@ public class DlgDelegate {
.setView( naView ) .setView( naView )
.setPositiveButton( android.R.string.ok, lstnr_p ); .setPositiveButton( android.R.string.ok, lstnr_p );
// Adding third button doesn't work for some reason. Either this
// feature goes away or the "do not show again" becomes a checkbox as
// many apps do it.
if ( null != state.m_pair ) { if ( null != state.m_pair ) {
final ActionPair more = state.m_pair; final ActionPair more = state.m_pair;
OnClickListener lstnr = new OnClickListener() { OnClickListener lstnr = new OnClickListener() {
public void onClick( DialogInterface dlg, int item ) { public void onClick( DialogInterface dlg, int item ) {
checkNotAgainCheck( state, naView );
m_clickCallback. m_clickCallback.
dlgButtonClicked( more.action, dlgButtonClicked( more.action,
AlertDialog.BUTTON_POSITIVE, AlertDialog.BUTTON_POSITIVE,
more.params ); more.params );
lstnr_p.onClick( dlg, AlertDialog.BUTTON_POSITIVE );
} }
}; };
builder.setNegativeButton( more.buttonStr, lstnr ); builder.setNegativeButton( more.buttonStr, lstnr );
@ -631,6 +629,10 @@ public class DlgDelegate {
items.add( getString( R.string.invite_choice_nfc ) ); items.add( getString( R.string.invite_choice_nfc ) );
means.add( DlgClickNotify.InviteMeans.NFC ); means.add( DlgClickNotify.InviteMeans.NFC );
} }
if ( XWApp.RELAYINVITE_SUPPORTED ) {
items.add( getString( R.string.invite_choice_relay ) );
means.add( DlgClickNotify.InviteMeans.RELAY );
}
final int clipPos = means.size(); final int clipPos = means.size();
items.add( getString( R.string.slmenu_copy_sel ) ); items.add( getString( R.string.slmenu_copy_sel ) );
means.add( DlgClickNotify.InviteMeans.CLIPBOARD ); means.add( DlgClickNotify.InviteMeans.CLIPBOARD );
@ -762,14 +764,7 @@ public class DlgDelegate {
OnClickListener cbkOnClickLstnr; OnClickListener cbkOnClickLstnr;
cbkOnClickLstnr = new OnClickListener() { cbkOnClickLstnr = new OnClickListener() {
public void onClick( DialogInterface dlg, int button ) { public void onClick( DialogInterface dlg, int button ) {
if ( null != naView && naView.getChecked() ) { checkNotAgainCheck( state, naView );
if ( 0 != state.m_prefsKey ) {
XWPrefs.setPrefsBoolean( m_activity, state.m_prefsKey,
true );
} else if ( null != state.m_onNAChecked ) {
state.m_onNAChecked.run();
}
}
if ( Action.SKIP_CALLBACK != state.m_action ) { if ( Action.SKIP_CALLBACK != state.m_action ) {
m_clickCallback.dlgButtonClicked( state.m_action, m_clickCallback.dlgButtonClicked( state.m_action,
@ -781,6 +776,18 @@ public class DlgDelegate {
return cbkOnClickLstnr; return cbkOnClickLstnr;
} }
private void checkNotAgainCheck( DlgState state, NotAgainView naView )
{
if ( null != naView && naView.getChecked() ) {
if ( 0 != state.m_prefsKey ) {
XWPrefs.setPrefsBoolean( m_activity, state.m_prefsKey,
true );
} else if ( null != state.m_onNAChecked ) {
state.m_onNAChecked.run();
}
}
}
private Dialog setCallbackDismissListener( final Dialog dialog, private Dialog setCallbackDismissListener( final Dialog dialog,
final DlgState state, final DlgState state,
DlgID dlgID ) DlgID dlgID )

View file

@ -70,4 +70,5 @@ public enum DlgID {
, DLG_GETDICT , DLG_GETDICT
, GAMES_LIST_NEWGAME , GAMES_LIST_NEWGAME
, CHANGE_CONN , CHANGE_CONN
, GAMES_LIST_NAME_REMATCH
} }

View file

@ -24,7 +24,7 @@ import android.app.Activity;
import android.os.Bundle; import android.os.Bundle;
import android.view.Window; import android.view.Window;
public class DwnldActivity extends XWListActivity { public class DwnldActivity extends XWActivity {
@Override @Override
protected void onCreate( Bundle savedInstanceState ) protected void onCreate( Bundle savedInstanceState )

View file

@ -21,7 +21,6 @@
package org.eehouse.android.xw4; package org.eehouse.android.xw4;
import android.app.Activity; import android.app.Activity;
import android.app.ListActivity;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.net.Uri; import android.net.Uri;
@ -69,7 +68,7 @@ public class DwnldDelegate extends ListDelegateBase {
void gotDictInfo( boolean success, String lc, String name ); void gotDictInfo( boolean success, String lc, String name );
} }
public DwnldDelegate( ListDelegator delegator, Bundle savedInstanceState ) public DwnldDelegate( Delegator delegator, Bundle savedInstanceState )
{ {
super( delegator, savedInstanceState, R.layout.import_dict ); super( delegator, savedInstanceState, R.layout.import_dict );
m_activity = delegator.getActivity(); m_activity = delegator.getActivity();

View file

@ -30,6 +30,7 @@ import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.os.Handler; import android.os.Handler;
import android.view.View; import android.view.View;
import java.lang.ref.WeakReference;
import junit.framework.Assert; import junit.framework.Assert;
public class ExpiringDelegate { public class ExpiringDelegate {
@ -210,7 +211,7 @@ public class ExpiringDelegate {
long nextStartIn = lastStart + onePct - now; long nextStartIn = lastStart + onePct - now;
// DbgUtils.logf( "pct change %d seconds from now", nextStartIn ); // DbgUtils.logf( "pct change %d seconds from now", nextStartIn );
m_handler.postDelayed( mkRunnable(), 1000 * nextStartIn ); // NPE m_handler.postDelayed( mkRunnable(), 1000 * nextStartIn );
} }
} }
} }
@ -218,27 +219,39 @@ public class ExpiringDelegate {
private Runnable mkRunnable() private Runnable mkRunnable()
{ {
if ( null == m_runnable ) { if ( null == m_runnable ) {
m_runnable = new Runnable() { m_runnable = mkRunnable( this );
public void run() { }
return m_runnable;
}
private static Runnable mkRunnable( ExpiringDelegate self )
{
final WeakReference<ExpiringDelegate> selfRef
= new WeakReference<ExpiringDelegate>( self );
return new Runnable() {
public void run() {
ExpiringDelegate dlgt = selfRef.get();
if ( null != dlgt ) {
if ( XWApp.DEBUG_EXP_TIMERS ) { if ( XWApp.DEBUG_EXP_TIMERS ) {
DbgUtils.logf( "ExpiringDelegate: timer fired" DbgUtils.logf( "ExpiringDelegate: timer fired"
+ " for %H", this ); + " for %H", this );
} }
if ( m_active ) { if ( dlgt.m_active ) {
figurePct(); dlgt.figurePct();
if ( m_haveTurnLocal ) { if ( dlgt.m_haveTurnLocal ) {
m_back = null; dlgt.m_back = null;
setBackground(); dlgt.setBackground();
} }
if ( XWApp.DEBUG_EXP_TIMERS ) { if ( XWApp.DEBUG_EXP_TIMERS ) {
DbgUtils.logf( "ExpiringDelegate: invalidating" DbgUtils.logf( "ExpiringDelegate: invalidating"
+ " view %H", m_view ); + " view %H", dlgt.m_view );
} }
m_view.invalidate(); dlgt.m_view.invalidate();
} }
} else {
DbgUtils.logf( "ExpiringDelegate.run(): reference gc'd" );
} }
}; }
} };
return m_runnable;
} }
} }

View file

@ -24,10 +24,6 @@ import android.app.AlertDialog;
import android.content.Context; import android.content.Context;
import android.webkit.WebView; import android.webkit.WebView;
import android.webkit.WebViewClient; import android.webkit.WebViewClient;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import org.eehouse.android.xw4.loc.LocUtils; import org.eehouse.android.xw4.loc.LocUtils;

View file

@ -44,7 +44,7 @@ public class GCMIntentService extends GCMBaseIntentService {
protected void onRegistered( Context context, String regId ) protected void onRegistered( Context context, String regId )
{ {
DbgUtils.logf( "GCMIntentService.onRegistered(%s)", regId ); DbgUtils.logf( "GCMIntentService.onRegistered(%s)", regId );
XWPrefs.setGCMDevID( context, regId ); DevID.setGCMDevID( context, regId );
notifyRelayService( context, true ); notifyRelayService( context, true );
} }
@ -52,7 +52,7 @@ public class GCMIntentService extends GCMBaseIntentService {
protected void onUnregistered( Context context, String regId ) protected void onUnregistered( Context context, String regId )
{ {
DbgUtils.logf( "GCMIntentService.onUnregistered(%s)", regId ); DbgUtils.logf( "GCMIntentService.onUnregistered(%s)", regId );
XWPrefs.clearGCMDevID( context ); DevID.clearGCMDevID( context );
RelayService.devIDChanged(); RelayService.devIDChanged();
notifyRelayService( context, false ); notifyRelayService( context, false );
} }
@ -122,7 +122,7 @@ public class GCMIntentService extends GCMBaseIntentService {
try { try {
GCMRegistrar.checkDevice( app ); GCMRegistrar.checkDevice( app );
// GCMRegistrar.checkManifest( app ); // GCMRegistrar.checkManifest( app );
String regId = XWPrefs.getGCMDevID( app ); String regId = DevID.getGCMDevID( app );
if (regId.equals("")) { if (regId.equals("")) {
GCMRegistrar.register( app, GCMConsts.SENDER_ID ); GCMRegistrar.register( app, GCMConsts.SENDER_ID );
} }

View file

@ -68,8 +68,6 @@ public class GameConfigDelegate extends DelegateBase
private static final String INTENT_FORRESULT_ROWID = "forresult"; private static final String INTENT_FORRESULT_ROWID = "forresult";
private static final String WHICH_PLAYER = "WHICH_PLAYER"; private static final String WHICH_PLAYER = "WHICH_PLAYER";
private static final int REQUEST_LANG = 1;
private static final int REQUEST_DICT = 2;
private Activity m_activity; private Activity m_activity;
private CheckBox m_joinPublicCheck; private CheckBox m_joinPublicCheck;
@ -94,7 +92,6 @@ public class GameConfigDelegate extends DelegateBase
private boolean m_forResult; private boolean m_forResult;
private CurGameInfo m_gi; private CurGameInfo m_gi;
private CurGameInfo m_giOrig; private CurGameInfo m_giOrig;
private GameLock m_gameLock;
private int m_whichPlayer; private int m_whichPlayer;
// private Spinner m_roleSpinner; // private Spinner m_roleSpinner;
// private Spinner m_connectSpinner; // private Spinner m_connectSpinner;
@ -509,10 +506,6 @@ public class GameConfigDelegate extends DelegateBase
protected void onPause() protected void onPause()
{ {
if ( null != m_gameLock ) {
m_gameLock.unlock();
m_gameLock = null;
}
m_giOrig = null; // flag for onStart and onResume m_giOrig = null; // flag for onStart and onResume
super.onPause(); super.onPause();
} }
@ -523,7 +516,7 @@ public class GameConfigDelegate extends DelegateBase
} }
@Override @Override
protected void onActivityResult( int requestCode, int resultCode, Intent data ) protected void onActivityResult( RequestCode requestCode, int resultCode, Intent data )
{ {
if ( Activity.RESULT_CANCELED != resultCode ) { if ( Activity.RESULT_CANCELED != resultCode ) {
loadGame(); loadGame();
@ -533,7 +526,7 @@ public class GameConfigDelegate extends DelegateBase
configDictSpinner( m_dictSpinner, m_gi.dictLang, dictName ); configDictSpinner( m_dictSpinner, m_gi.dictLang, dictName );
configDictSpinner( m_playerDictSpinner, m_gi.dictLang, dictName ); configDictSpinner( m_playerDictSpinner, m_gi.dictLang, dictName );
break; break;
case REQUEST_LANG: case REQUEST_LANG_GC:
String langName = data.getStringExtra( DictsDelegate.RESULT_LAST_LANG ); String langName = data.getStringExtra( DictsDelegate.RESULT_LAST_LANG );
selLangChanged( langName ); selLangChanged( langName );
setLangSpinnerSelection( langName ); setLangSpinnerSelection( langName );
@ -549,10 +542,8 @@ public class GameConfigDelegate extends DelegateBase
if ( null == m_giOrig ) { if ( null == m_giOrig ) {
m_giOrig = new CurGameInfo( m_activity ); m_giOrig = new CurGameInfo( m_activity );
// Lock in case we're going to config. We *could* re-get the GameLock gameLock = new GameLock( m_rowid, false ).lock();
// lock once the user decides to make changes. PENDING. int gamePtr = GameUtils.loadMakeGame( m_activity, m_giOrig, gameLock );
m_gameLock = new GameLock( m_rowid, true ).lock();
int gamePtr = GameUtils.loadMakeGame( m_activity, m_giOrig, m_gameLock );
if ( 0 == gamePtr ) { if ( 0 == gamePtr ) {
showDictGoneFinish(); showDictGoneFinish();
} else { } else {
@ -564,7 +555,6 @@ public class GameConfigDelegate extends DelegateBase
m_gameLockedCheck = m_gameLockedCheck =
(CheckBox)findViewById( R.id.game_locked_check ); (CheckBox)findViewById( R.id.game_locked_check );
m_gameLockedCheck.setVisibility( View.VISIBLE ); m_gameLockedCheck.setVisibility( View.VISIBLE );
m_gameLockedCheck.setChecked( true );
m_gameLockedCheck.setOnClickListener( this ); m_gameLockedCheck.setOnClickListener( this );
} }
handleLockedChange(); handleLockedChange();
@ -581,11 +571,14 @@ public class GameConfigDelegate extends DelegateBase
} else if ( !localOnlyGame() ) { } else if ( !localOnlyGame() ) {
String relayName = XWPrefs.getDefaultRelayHost( m_activity ); String relayName = XWPrefs.getDefaultRelayHost( m_activity );
int relayPort = XWPrefs.getDefaultRelayPort( m_activity ); int relayPort = XWPrefs.getDefaultRelayPort( m_activity );
XwJNI.comms_getInitialAddr( m_carOrig, relayName, relayPort ); XwJNI.comms_getInitialAddr( m_carOrig, relayName,
relayPort );
} }
m_conTypes = (CommsConnTypeSet)m_carOrig.conTypes.clone(); m_conTypes = (CommsConnTypeSet)m_carOrig.conTypes.clone();
XwJNI.game_dispose( gamePtr ); XwJNI.game_dispose( gamePtr );
gameLock.unlock();
m_car = new CommsAddrRec( m_carOrig ); m_car = new CommsAddrRec( m_carOrig );
setTitle(); setTitle();
@ -698,7 +691,7 @@ public class GameConfigDelegate extends DelegateBase
public void onClick( View view ) public void onClick( View view )
{ {
if ( null == m_gameLock ) { if ( isFinishing() ) {
// do nothing; we're on the way out // do nothing; we're on the way out
} else if ( m_addPlayerButton == view ) { } else if ( m_addPlayerButton == view ) {
int curIndex = m_gi.nPlayers; int curIndex = m_gi.nPlayers;
@ -756,7 +749,7 @@ public class GameConfigDelegate extends DelegateBase
protected boolean onBackPressed() protected boolean onBackPressed()
{ {
boolean consumed = false; boolean consumed = false;
if ( null != m_gameLock ) { if ( ! isFinishing() ) {
if ( m_forResult ) { if ( m_forResult ) {
deleteGame(); deleteGame();
} else { } else {
@ -778,63 +771,64 @@ public class GameConfigDelegate extends DelegateBase
private void deleteGame() private void deleteGame()
{ {
if ( null != m_gameLock ) { GameUtils.deleteGame( m_activity, m_rowid, false );
DBUtils.deleteGame( m_activity, m_gameLock );
m_gameLock.unlock();
m_gameLock = null;
}
} }
private void loadPlayersList() private void loadPlayersList()
{ {
m_playerLayout.removeAllViews(); if ( !isFinishing() ) {
m_playerLayout.removeAllViews();
String[] names = m_gi.visibleNames( false ); String[] names = m_gi.visibleNames( false );
// only enable delete if one will remain (or two if networked) // only enable delete if one will remain (or two if networked)
boolean canDelete = names.length > 2 boolean canDelete = names.length > 2
|| (localOnlyGame() && names.length > 1); || (localOnlyGame() && names.length > 1);
View.OnClickListener lstnr = new View.OnClickListener() { View.OnClickListener lstnr = new View.OnClickListener() {
@Override @Override
public void onClick( View view ) { public void onClick( View view ) {
m_whichPlayer = ((XWListItem)view).getPosition(); m_whichPlayer = ((XWListItem)view).getPosition();
showDialog( DlgID.PLAYER_EDIT ); showDialog( DlgID.PLAYER_EDIT );
}
};
boolean localGame = localOnlyGame();
boolean unlocked = null == m_gameLockedCheck
|| !m_gameLockedCheck.isChecked();
for ( int ii = 0; ii < names.length; ++ii ) {
final XWListItem view = XWListItem.inflate( m_activity, null );
view.setPosition( ii );
view.setText( names[ii] );
if ( localGame && m_gi.players[ii].isLocal ) {
view.setComment( m_gi.dictName(ii) );
}
if ( canDelete ) {
view.setDeleteCallback( this );
} }
};
boolean localGame = localOnlyGame(); view.setEnabled( unlocked );
for ( int ii = 0; ii < names.length; ++ii ) { view.setOnClickListener( lstnr );
final XWListItem view = XWListItem.inflate( m_activity, null ); m_playerLayout.addView( view );
view.setPosition( ii );
view.setText( names[ii] ); View divider = inflate( R.layout.divider_view );
if ( localGame && m_gi.players[ii].isLocal ) { m_playerLayout.addView( divider );
view.setComment( m_gi.dictName(ii) );
}
if ( canDelete ) {
view.setDeleteCallback( this );
} }
view.setOnClickListener( lstnr ); m_addPlayerButton
m_playerLayout.addView( view ); .setVisibility( names.length >= CurGameInfo.MAX_NUM_PLAYERS?
View.GONE : View.VISIBLE );
m_jugglePlayersButton
.setVisibility( names.length <= 1 ?
View.GONE : View.VISIBLE );
View divider = inflate( R.layout.divider_view ); showHideRelayStuff();
m_playerLayout.addView( divider );
if ( ! localOnlyGame()
&& ((0 == m_gi.remoteCount() )
|| (m_gi.nPlayers == m_gi.remoteCount()) ) ) {
showDialog( DlgID.FORCE_REMOTE );
}
adjustPlayersLabel();
} }
m_addPlayerButton
.setVisibility( names.length >= CurGameInfo.MAX_NUM_PLAYERS?
View.GONE : View.VISIBLE );
m_jugglePlayersButton
.setVisibility( names.length <= 1 ?
View.GONE : View.VISIBLE );
showHideRelayStuff();
if ( ! localOnlyGame()
&& ((0 == m_gi.remoteCount() )
|| (m_gi.nPlayers == m_gi.remoteCount()) ) ) {
showDialog( DlgID.FORCE_REMOTE );
}
adjustPlayersLabel();
} // loadPlayersList } // loadPlayersList
private void configDictSpinner( Spinner dictsSpinner, int lang, private void configDictSpinner( Spinner dictsSpinner, int lang,
@ -856,7 +850,7 @@ public class GameConfigDelegate extends DelegateBase
if ( chosen.equals( m_browseText ) ) { if ( chosen.equals( m_browseText ) ) {
DictsDelegate.downloadForResult( m_activity, DictsDelegate.downloadForResult( m_activity,
REQUEST_DICT, RequestCode.REQUEST_DICT,
m_gi.dictLang ); m_gi.dictLang );
} }
} }
@ -885,13 +879,17 @@ public class GameConfigDelegate extends DelegateBase
public void onItemSelected(AdapterView<?> parentView, public void onItemSelected(AdapterView<?> parentView,
View selectedItemView, View selectedItemView,
int position, long id ) { int position, long id ) {
String chosen = if ( ! isFinishing() ) { // not on the way out?
(String)parentView.getItemAtPosition( position ); String chosen =
if ( chosen.equals( m_browseText ) ) { (String)parentView.getItemAtPosition( position );
DictsDelegate.downloadForResult( m_activity, REQUEST_LANG ); if ( chosen.equals( m_browseText ) ) {
} else { DictsDelegate.downloadForResult( m_activity,
String langName = adapter.getLangAtPosition( position ); RequestCode
selLangChanged( langName ); .REQUEST_LANG_GC );
} else {
String langName = adapter.getLangAtPosition( position );
selLangChanged( langName );
}
} }
} }
@ -1044,7 +1042,15 @@ public class GameConfigDelegate extends DelegateBase
m_isLocked = locking; m_isLocked = locking;
for ( int id : s_disabledWhenLocked ) { for ( int id : s_disabledWhenLocked ) {
View view = findViewById( id ); View view = findViewById( id );
view.setEnabled( !m_isLocked ); view.setEnabled( !locking );
}
int nChildren = m_playerLayout.getChildCount();
for ( int ii = 0; ii < nChildren; ++ii ) {
View child = m_playerLayout.getChildAt( ii );
if ( child instanceof XWListItem ) {
((XWListItem)child).setEnabled( !locking );
}
} }
} }
@ -1146,9 +1152,12 @@ public class GameConfigDelegate extends DelegateBase
private void applyChanges( boolean forceNew ) private void applyChanges( boolean forceNew )
{ {
if ( null != m_gameLock ) { if ( !isFinishing() ) {
GameUtils.applyChanges( m_activity, m_gi, m_car, m_gameLock, GameLock gameLock = new GameLock( m_rowid, true ).lock();
GameUtils.applyChanges( m_activity, m_gi, m_car, gameLock,
forceNew ); forceNew );
DBUtils.saveThumbnail( m_activity, gameLock, null ); // clear it
gameLock.unlock();
} }
} }
@ -1158,8 +1167,6 @@ public class GameConfigDelegate extends DelegateBase
&& 0 == m_car.ip_relay_invite.length() ) { && 0 == m_car.ip_relay_invite.length() ) {
showOKOnlyDialog( R.string.no_empty_rooms ); showOKOnlyDialog( R.string.no_empty_rooms );
} else { } else {
m_gameLock.unlock();
m_gameLock = null;
GameUtils.launchGameAndFinish( m_activity, m_rowid ); GameUtils.launchGameAndFinish( m_activity, m_rowid );
} }
} }
@ -1189,14 +1196,14 @@ public class GameConfigDelegate extends DelegateBase
return DeviceRole.SERVER_STANDALONE == m_giOrig.serverRole; return DeviceRole.SERVER_STANDALONE == m_giOrig.serverRole;
} }
public static void editForResult( Activity parent, int requestCode, public static void editForResult( Activity parent, RequestCode requestCode,
long rowID ) long rowID )
{ {
Intent intent = new Intent( parent, GameConfigActivity.class ); Intent intent = new Intent( parent, GameConfigActivity.class );
intent.setAction( Intent.ACTION_EDIT ); intent.setAction( Intent.ACTION_EDIT );
intent.putExtra( GameUtils.INTENT_KEY_ROWID, rowID ); intent.putExtra( GameUtils.INTENT_KEY_ROWID, rowID );
intent.putExtra( INTENT_FORRESULT_ROWID, true ); intent.putExtra( INTENT_FORRESULT_ROWID, true );
parent.startActivityForResult( intent, requestCode ); parent.startActivityForResult( intent, requestCode.ordinal() );
} }
private void setConnLabel() private void setConnLabel()

View file

@ -139,8 +139,11 @@ public class GameListGroup extends ExpiringLinearLayout
////////////////////////////////////////////////// //////////////////////////////////////////////////
public boolean onLongClick( View view ) public boolean onLongClick( View view )
{ {
longClicked(); boolean handled = ! XWApp.CONTEXT_MENUS_ENABLED;
return true; if ( handled ) {
longClicked();
}
return handled;
} }
////////////////////////////////////////////////// //////////////////////////////////////////////////

View file

@ -124,7 +124,7 @@ public class GameListItem extends LinearLayout
++m_loadingCount; ++m_loadingCount;
LoadItemTask task = new LoadItemTask(); LoadItemTask task = new LoadItemTask();
if ( Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB ) { if ( false && Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB ) {
// Actually run these in parallel if the OS supports it // Actually run these in parallel if the OS supports it
task.executeOnExecutor( AsyncTask.THREAD_POOL_EXECUTOR ); task.executeOnExecutor( AsyncTask.THREAD_POOL_EXECUTOR );
} else { } else {

View file

@ -32,7 +32,7 @@ public class GameLock {
private long m_rowid; private long m_rowid;
private boolean m_isForWrite; private boolean m_isForWrite;
private int m_lockCount; private int m_lockCount;
StackTraceElement[] m_lockTrace; private StackTraceElement[] m_lockTrace;
private static HashMap<Long, GameLock> private static HashMap<Long, GameLock>
s_locks = new HashMap<Long,GameLock>(); s_locks = new HashMap<Long,GameLock>();
@ -54,9 +54,15 @@ public class GameLock {
// see if not doing that causes problems. // see if not doing that causes problems.
public boolean tryLock() public boolean tryLock()
{ {
return null == tryLockImpl(); // unowned?
}
private GameLock tryLockImpl()
{
GameLock owner = null;
boolean gotIt = false; boolean gotIt = false;
synchronized( s_locks ) { synchronized( s_locks ) {
GameLock owner = s_locks.get( m_rowid ); owner = s_locks.get( m_rowid );
if ( null == owner ) { // unowned if ( null == owner ) { // unowned
Assert.assertTrue( 0 == m_lockCount ); Assert.assertTrue( 0 == m_lockCount );
s_locks.put( m_rowid, this ); s_locks.put( m_rowid, this );
@ -64,8 +70,8 @@ public class GameLock {
gotIt = true; gotIt = true;
if ( XWApp.DEBUG_LOCKS ) { if ( XWApp.DEBUG_LOCKS ) {
StackTraceElement[] trace = Thread.currentThread(). StackTraceElement[] trace
getStackTrace(); = Thread.currentThread().getStackTrace();
m_lockTrace = new StackTraceElement[trace.length]; m_lockTrace = new StackTraceElement[trace.length];
System.arraycopy( trace, 0, m_lockTrace, 0, trace.length ); System.arraycopy( trace, 0, m_lockTrace, 0, trace.length );
} }
@ -76,13 +82,17 @@ public class GameLock {
Assert.assertTrue( 0 == m_lockCount ); Assert.assertTrue( 0 == m_lockCount );
++m_lockCount; ++m_lockCount;
gotIt = true; gotIt = true;
owner = null;
} else if ( XWApp.DEBUG_LOCKS ) {
DbgUtils.logf( "tryLock(): rowid %d already held by lock %H",
m_rowid, owner );
} }
} }
if ( XWApp.DEBUG_LOCKS ) { if ( XWApp.DEBUG_LOCKS ) {
DbgUtils.logf( "GameLock.tryLock %H (rowid=%d) => %b", DbgUtils.logf( "GameLock.tryLock %H (rowid=%d) => %b",
this, m_rowid, gotIt ); this, m_rowid, gotIt );
} }
return gotIt; return owner;
} }
// Wait forever (but may assert if too long) // Wait forever (but may assert if too long)
@ -105,15 +115,16 @@ public class GameLock {
} }
for ( ; ; ) { for ( ; ; ) {
if ( tryLock() ) { GameLock curOwner = tryLockImpl();
if ( null == curOwner ) {
result = this; result = this;
break; break;
} }
if ( XWApp.DEBUG_LOCKS ) { if ( XWApp.DEBUG_LOCKS ) {
DbgUtils.logf( "GameLock.lock() %H failed; sleeping", this ); DbgUtils.logf( "GameLock.lock() %H failed; sleeping", this );
if ( 0 == sleptTime || sleptTime + SLEEP_TIME >= assertTime ) { if ( 0 == sleptTime || sleptTime + SLEEP_TIME >= assertTime ) {
DbgUtils.logf( "lock holding stack:", this ); DbgUtils.logf( "lock %H seeking stack:", curOwner );
DbgUtils.printStack( m_lockTrace ); DbgUtils.printStack( curOwner.m_lockTrace );
DbgUtils.logf( "lock %H seeking stack:", this ); DbgUtils.logf( "lock %H seeking stack:", this );
DbgUtils.printStack(); DbgUtils.printStack();
} }

View file

@ -260,7 +260,7 @@ public class GameUtils {
return result; return result;
} }
public static String makeDefaultName( Context context, boolean isSolo ) public static String makeDefaultName( Context context )
{ {
int count = DBUtils.getIncrementIntFor( context, DBUtils.KEY_NEWGAMECOUNT, 0, 1 ); int count = DBUtils.getIncrementIntFor( context, DBUtils.KEY_NEWGAMECOUNT, 0, 1 );
return LocUtils.getString( context, R.string.game_fmt, count ); return LocUtils.getString( context, R.string.game_fmt, count );
@ -455,18 +455,20 @@ public class GameUtils {
public static long makeNewMultiGame( Context context, NetLaunchInfo nli ) public static long makeNewMultiGame( Context context, NetLaunchInfo nli )
{ {
return makeNewMultiGame( context, nli, null ); return makeNewMultiGame( context, nli, (MultiMsgSink)null,
(UtilCtxt)null );
} }
public static long makeNewMultiGame( Context context, NetLaunchInfo nli, public static long makeNewMultiGame( Context context, NetLaunchInfo nli,
MultiMsgSink sink ) MultiMsgSink sink, UtilCtxt util )
{ {
DbgUtils.logf( "makeNewMultiGame(nli=%s)", nli.toString() ); DbgUtils.logf( "makeNewMultiGame(nli=%s)", nli.toString() );
CommsAddrRec addr = nli.makeAddrRec( context ); CommsAddrRec addr = nli.makeAddrRec( context );
return makeNewMultiGame( context, sink, DBUtils.GROUPID_UNSPEC, addr, return makeNewMultiGame( context, sink, util, DBUtils.GROUPID_UNSPEC,
new int[] {nli.lang}, new String[] { nli.dict }, addr, new int[] {nli.lang},
nli.nPlayersT, nli.nPlayersH, nli.forceChannel, new String[] { nli.dict }, nli.nPlayersT,
nli.nPlayersH, nli.forceChannel,
nli.inviteID(), nli.gameID(), nli.gameName, nli.inviteID(), nli.gameID(), nli.gameName,
false ); false );
} }
@ -474,35 +476,41 @@ public class GameUtils {
public static long makeNewMultiGame( Context context, long groupID, public static long makeNewMultiGame( Context context, long groupID,
String gameName ) String gameName )
{ {
return makeNewMultiGame( context, groupID, (CommsConnTypeSet)null, return makeNewMultiGame( context, groupID, null, 0,
gameName ); (CommsConnTypeSet)null, gameName );
} }
public static long makeNewMultiGame( Context context, long groupID, public static long makeNewMultiGame( Context context, long groupID,
CommsConnTypeSet addrSet, String gameName ) String dict, int lang,
CommsConnTypeSet addrSet,
String gameName )
{ {
String inviteID = makeRandomID(); String inviteID = makeRandomID();
return makeNewMultiGame( context, groupID, inviteID, addrSet, gameName ); return makeNewMultiGame( context, groupID, inviteID, dict, lang,
addrSet, gameName );
} }
private static long makeNewMultiGame( Context context, long groupID, private static long makeNewMultiGame( Context context, long groupID,
String inviteID, CommsConnTypeSet addrSet, String inviteID, String dict,
int lang, CommsConnTypeSet addrSet,
String gameName ) String gameName )
{ {
int[] lang = {0}; int[] langArray = {lang};
String[] dict = {null}; String[] dictArray = {dict};
if ( null == addrSet ) { if ( null == addrSet ) {
addrSet = XWPrefs.getAddrTypes( context ); addrSet = XWPrefs.getAddrTypes( context );
} }
CommsAddrRec addr = new CommsAddrRec( addrSet ); CommsAddrRec addr = new CommsAddrRec( addrSet );
addr.populate( context ); addr.populate( context );
int forceChannel = 0; int forceChannel = 0;
return makeNewMultiGame( context, null, groupID, addr, lang, dict, 2, 1, return makeNewMultiGame( context, (MultiMsgSink)null, (UtilCtxt)null,
groupID, addr, langArray, dictArray, 2, 1,
forceChannel, inviteID, 0, gameName, true ); forceChannel, inviteID, 0, gameName, true );
} }
private static long makeNewMultiGame( Context context, MultiMsgSink sink, private static long makeNewMultiGame( Context context, MultiMsgSink sink,
long groupID, CommsAddrRec addr, UtilCtxt util, long groupID,
CommsAddrRec addr,
int[] lang, String[] dict, int[] lang, String[] dict,
int nPlayersT, int nPlayersH, int nPlayersT, int nPlayersH,
int forceChannel, String inviteID, int forceChannel, String inviteID,
@ -535,7 +543,7 @@ public class GameUtils {
if ( DBUtils.ROWID_NOTFOUND != rowid ) { if ( DBUtils.ROWID_NOTFOUND != rowid ) {
GameLock lock = new GameLock( rowid, true ).lock(); GameLock lock = new GameLock( rowid, true ).lock();
applyChanges( context, sink, gi, addr, inviteID, lock, false ); applyChanges( context, sink, gi, util, addr, inviteID, lock, false );
lock.unlock(); lock.unlock();
} }
@ -587,9 +595,10 @@ public class GameUtils {
addr = new CommsAddrRec( null, null ); addr = new CommsAddrRec( null, null );
} }
String inviteID = GameUtils.formatGameID( gameID ); String inviteID = GameUtils.formatGameID( gameID );
return makeNewMultiGame( context, sink, groupID, addr, langa, dicta, return makeNewMultiGame( context, sink, (UtilCtxt)null, groupID, addr,
nPlayersT, nPlayersH, forceChannel, langa, dicta, nPlayersT, nPlayersH,
inviteID, gameID, gameName, isHost ); forceChannel, inviteID, gameID, gameName,
isHost );
} }
// @SuppressLint({ "NewApi", "NewApi", "NewApi", "NewApi" }) // @SuppressLint({ "NewApi", "NewApi", "NewApi", "NewApi" })
@ -987,13 +996,14 @@ public class GameUtils {
CommsAddrRec car, String inviteID, CommsAddrRec car, String inviteID,
GameLock lock, boolean forceNew ) GameLock lock, boolean forceNew )
{ {
applyChanges( context, null, gi, car, inviteID, lock, forceNew ); applyChanges( context, (MultiMsgSink)null, gi, (UtilCtxt)null, car,
inviteID, lock, forceNew );
} }
public static void applyChanges( Context context, MultiMsgSink sink, public static void applyChanges( Context context, MultiMsgSink sink,
CurGameInfo gi, CommsAddrRec car, CurGameInfo gi, UtilCtxt util,
String inviteID, GameLock lock, CommsAddrRec car, String inviteID,
boolean forceNew ) GameLock lock, boolean forceNew )
{ {
// This should be a separate function, commitChanges() or // This should be a separate function, commitChanges() or
// somesuch. But: do we have a way to save changes to a gi // somesuch. But: do we have a way to save changes to a gi
@ -1020,7 +1030,9 @@ public class GameUtils {
} }
if ( forceNew || !madeGame ) { if ( forceNew || !madeGame ) {
XwJNI.game_makeNewGame( gamePtr, gi, JNIUtilsImpl.get(context), XwJNI.game_makeNewGame( gamePtr, gi, util,
JNIUtilsImpl.get(context),
(DrawCtx)null,
cp, sink, dictNames, pairs.m_bytes, cp, sink, dictNames, pairs.m_bytes,
pairs.m_paths, langName ); pairs.m_paths, langName );
} }
@ -1078,8 +1090,8 @@ public class GameUtils {
{ {
if ( null != bmr ) { if ( null != bmr ) {
Intent intent = GamesListDelegate.makeRowidIntent( context, rowid ); Intent intent = GamesListDelegate.makeRowidIntent( context, rowid );
String msg; String msg = null;
int titleID; int titleID = 0;
if ( null != bmr.m_chat ) { if ( null != bmr.m_chat ) {
titleID = R.string.notify_chat_title_fmt; titleID = R.string.notify_chat_title_fmt;
if ( null != bmr.m_chatFrom ) { if ( null != bmr.m_chatFrom ) {
@ -1089,19 +1101,30 @@ public class GameUtils {
} else { } else {
msg = bmr.m_chat; msg = bmr.m_chat;
} }
} else { } else if ( null != bmr.m_lmi ) {
titleID = R.string.notify_title_fmt; titleID = R.string.notify_title_fmt;
msg = bmr.m_lmi.format( context ); msg = bmr.m_lmi.format( context ); // NPE
}
if ( 0 != titleID ) {
String title = LocUtils.getString( context, titleID,
getName( context, rowid ) );
Utils.postNotification( context, intent, title, msg, (int)rowid );
} }
String title = LocUtils.getString( context, titleID,
getName( context, rowid ) );
Utils.postNotification( context, intent, title, msg, (int)rowid );
} else { } else {
DbgUtils.logdf( "postMoveNotification(): posting nothing for lack" DbgUtils.logdf( "postMoveNotification(): posting nothing for lack"
+ " of brm" ); + " of brm" );
} }
} }
public static void postInvitedNotification( Context context, int gameID,
String body, long rowid )
{
Intent intent = GamesListDelegate.makeGameIDIntent( context, gameID );
Utils.postNotification( context, intent, R.string.invite_notice_title,
body, (int)rowid );
}
private static void tellDied( Context context, GameLock lock, private static void tellDied( Context context, GameLock lock,
boolean informNow ) boolean informNow )
{ {

View file

@ -29,14 +29,7 @@ import org.eehouse.android.xw4.jni.CurGameInfo;
import junit.framework.Assert; import junit.framework.Assert;
public class GamesListActivity extends XWListActivity { public class GamesListActivity extends XWActivity {
// private static final String RELAYIDS_EXTRA = "relayids";
private static final String ROWID_EXTRA = "rowid";
private static final String GAMEID_EXTRA = "gameid";
private static final String REMATCH_ROWID_EXTRA = "rowid_rm";
private static final String ALERT_MSG = "alert_msg";
private GamesListDelegate m_dlgt; private GamesListDelegate m_dlgt;
@Override @Override

View file

@ -24,12 +24,14 @@ import android.app.Activity;
import android.app.AlertDialog; import android.app.AlertDialog;
import android.app.Dialog; import android.app.Dialog;
import android.content.Context; import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener; import android.content.DialogInterface.OnClickListener;
import android.content.DialogInterface;
import android.content.Intent; import android.content.Intent;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler; import android.os.Handler;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.ContextMenu;
import android.view.Menu; import android.view.Menu;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.View; import android.view.View;
@ -80,18 +82,19 @@ public class GamesListDelegate extends ListDelegateBase
private static final String SAVE_DICTNAMES = "SAVE_DICTNAMES"; private static final String SAVE_DICTNAMES = "SAVE_DICTNAMES";
private static final String SAVE_NEXTSOLO = "SAVE_NEXTSOLO"; private static final String SAVE_NEXTSOLO = "SAVE_NEXTSOLO";
private static final int REQUEST_LANG = 1;
private static final int CONFIG_GAME = 2;
private static final String RELAYIDS_EXTRA = "relayids"; private static final String RELAYIDS_EXTRA = "relayids";
private static final String ROWID_EXTRA = "rowid"; private static final String ROWID_EXTRA = "rowid";
private static final String GAMEID_EXTRA = "gameid"; private static final String GAMEID_EXTRA = "gameid";
private static final String REMATCH_ROWID_EXTRA = "rm_rowid"; private static final String REMATCH_ROWID_EXTRA = "rm_rowid";
private static final String REMATCH_DICT_EXTRA = "rm_dict";
private static final String REMATCH_LANG_EXTRA = "rm_lang";
private static final String REMATCH_NEWNAME_EXTRA = "rm_nnm";
private static final String REMATCH_ADDRS_EXTRA = "rm_addrs"; private static final String REMATCH_ADDRS_EXTRA = "rm_addrs";
private static final String REMATCH_BTADDR_EXTRA = "rm_btaddr"; private static final String REMATCH_BTADDR_EXTRA = "rm_btaddr";
private static final String REMATCH_PHONE_EXTRA = "rm_phone"; private static final String REMATCH_PHONE_EXTRA = "rm_phone";
private static final String REMATCH_RELAYID_EXTRA = "rm_relayid"; private static final String REMATCH_RELAYID_EXTRA = "rm_relayid";
private static final String ALERT_MSG = "alert_msg"; private static final String ALERT_MSG = "alert_msg";
private class GameListAdapter extends XWExpListAdapter { private class GameListAdapter extends XWExpListAdapter {
@ -561,7 +564,7 @@ public class GamesListDelegate extends ListDelegateBase
private Activity m_activity; private Activity m_activity;
private static GamesListDelegate s_self; private static GamesListDelegate s_self;
private ListDelegator m_delegator; private Delegator m_delegator;
private GameListAdapter m_adapter; private GameListAdapter m_adapter;
private Handler m_handler; private Handler m_handler;
private String m_missingDict; private String m_missingDict;
@ -585,8 +588,10 @@ public class GamesListDelegate extends ListDelegateBase
private boolean m_nextIsSolo; private boolean m_nextIsSolo;
private Button[] m_newGameButtons; private Button[] m_newGameButtons;
private boolean m_haveShownGetDict; private boolean m_haveShownGetDict;
private Intent m_rematchIntent;
private Object[] m_newGameParams;
public GamesListDelegate( ListDelegator delegator, Bundle sis ) public GamesListDelegate( Delegator delegator, Bundle sis )
{ {
super( delegator, sis, R.layout.game_list, R.menu.games_list_menu ); super( delegator, sis, R.layout.game_list, R.menu.games_list_menu );
m_delegator = delegator; m_delegator = delegator;
@ -612,7 +617,9 @@ public class GamesListDelegate extends ListDelegateBase
public void onClick( DialogInterface dlg, int item ) { public void onClick( DialogInterface dlg, int item ) {
// no name, so user must pick // no name, so user must pick
if ( null == m_missingDictName ) { if ( null == m_missingDictName ) {
DictsDelegate.downloadForResult( m_activity, REQUEST_LANG, DictsDelegate.downloadForResult( m_activity,
RequestCode
.REQUEST_LANG_GL,
m_missingDictLang ); m_missingDictLang );
} else { } else {
DwnldDelegate DwnldDelegate
@ -710,7 +717,7 @@ public class GamesListDelegate extends ListDelegateBase
String name = m_namer.getName(); String name = m_namer.getName();
DBUtils.setGroupName( m_activity, DBUtils.setGroupName( m_activity,
m_groupid, name ); m_groupid, name );
m_adapter.reloadGame( m_rowid ); reloadGame( m_rowid );
mkListAdapter(); mkListAdapter();
} }
}; };
@ -810,7 +817,7 @@ public class GamesListDelegate extends ListDelegateBase
} }
CommonPrefs.setDefaultPlayerName( m_activity, name ); CommonPrefs.setDefaultPlayerName( m_activity, name );
getDictForLangIf(); // hack!!! makeThenLaunchOrConfigure();
} }
}); });
break; break;
@ -821,12 +828,12 @@ public class GamesListDelegate extends ListDelegateBase
final EditText edit = (EditText)view.findViewById( R.id.edit ); final EditText edit = (EditText)view.findViewById( R.id.edit );
lstnr = new OnClickListener() { lstnr = new OnClickListener() {
public void onClick( DialogInterface dlg, int item ) { public void onClick( DialogInterface dlg, int item ) {
makeThenLaunchOrConfigure( edit, true ); makeThenLaunchOrConfigure( edit, true, false );
} }
}; };
lstnr2 = new OnClickListener() { lstnr2 = new OnClickListener() {
public void onClick( DialogInterface dlg, int item ) { public void onClick( DialogInterface dlg, int item ) {
makeThenLaunchOrConfigure( edit, false ); makeThenLaunchOrConfigure( edit, false, false );
} }
}; };
@ -839,6 +846,23 @@ public class GamesListDelegate extends ListDelegateBase
.create(); .create();
break; break;
case GAMES_LIST_NAME_REMATCH:
view = (LinearLayout)
LocUtils.inflate( m_activity, R.layout.msg_label_and_edit );
dialog = makeAlertBuilder()
.setView( view )
.setTitle( R.string.button_rematch )
.setIcon( R.drawable.sologame__gen )
.setPositiveButton( android.R.string.ok, new OnClickListener() {
public void onClick( DialogInterface dlg, int item ) {
EditText edit = (EditText)((Dialog)dlg)
.findViewById( R.id.edit );
startRematchWithName( edit );
}
} )
.create();
break;
default: default:
dialog = super.onCreateDialog( id ); dialog = super.onCreateDialog( id );
break; break;
@ -860,25 +884,38 @@ public class GamesListDelegate extends ListDelegateBase
ad.getButton( AlertDialog.BUTTON_NEGATIVE ) ad.getButton( AlertDialog.BUTTON_NEGATIVE )
.setVisibility( canDoDefaults ? View.VISIBLE : View.GONE ); .setVisibility( canDoDefaults ? View.VISIBLE : View.GONE );
ad.setIcon( m_nextIsSolo ? R.drawable.sologame__gen
: R.drawable.multigame__gen );
ad.setTitle( m_nextIsSolo ? R.string.new_game
: R.string.new_game_networked);
String msg = getString( canDoDefaults ? R.string.new_game_message String msg = getString( canDoDefaults ? R.string.new_game_message
: R.string.new_game_message_nodflt ); : R.string.new_game_message_nodflt );
if ( m_nextIsSolo ) { if ( !m_nextIsSolo ) {
ad.setTitle( R.string.new_game );
ad.setIcon( R.drawable.sologame__gen );
} else {
ad.setTitle( R.string.new_game_networked );
ad.setIcon( R.drawable.multigame__gen );
msg += "\n\n" + getString( R.string.new_game_message_net ); msg += "\n\n" + getString( R.string.new_game_message_net );
} }
TextView edit = (TextView)dialog.findViewById( R.id.msg ); TextView edit = (TextView)dialog.findViewById( R.id.msg );
edit.setText( msg ); edit.setText( msg );
edit = (TextView)dialog.findViewById( R.id.edit ); edit = (TextView)dialog.findViewById( R.id.edit );
edit.setText( GameUtils.makeDefaultName( m_activity, m_nextIsSolo ) ); edit.setText( GameUtils.makeDefaultName( m_activity ) );
edit.setVisibility( View.VISIBLE );
break;
case GAMES_LIST_NAME_REMATCH:
edit = (TextView)dialog.findViewById( R.id.edit );
edit.setText( m_rematchIntent
.getStringExtra( REMATCH_NEWNAME_EXTRA ) );
boolean solo =
-1 == m_rematchIntent.getIntExtra( REMATCH_ADDRS_EXTRA, -1 );
ad.setIcon( solo ? R.drawable.sologame__gen
: R.drawable.multigame__gen );
((TextView)dialog.findViewById( R.id.msg ))
.setVisibility( View.GONE );
break; break;
} }
} }
@Override
protected void init( Bundle savedInstanceState ) protected void init( Bundle savedInstanceState )
{ {
m_handler = new Handler(); m_handler = new Handler();
@ -914,9 +951,7 @@ public class GamesListDelegate extends ListDelegateBase
tryStartsFromIntent( getIntent() ); tryStartsFromIntent( getIntent() );
if ( !askDefaultNameIf() ) { getDictForLangIf();
getDictForLangIf();
}
m_origTitle = getTitle(); m_origTitle = getTitle();
} // init } // init
@ -929,7 +964,7 @@ public class GamesListDelegate extends ListDelegateBase
m_launchedGames.clear(); m_launchedGames.clear();
Assert.assertNotNull( intent ); Assert.assertNotNull( intent );
invalRelayIDs( intent.getStringArrayExtra( RELAYIDS_EXTRA ) ); invalRelayIDs( intent.getStringArrayExtra( RELAYIDS_EXTRA ) );
m_adapter.reloadGame( intent.getLongExtra( ROWID_EXTRA, -1 ) ); reloadGame( intent.getLongExtra( ROWID_EXTRA, -1 ) );
tryStartsFromIntent( intent ); tryStartsFromIntent( intent );
} }
@ -1019,7 +1054,8 @@ public class GamesListDelegate extends ListDelegateBase
// OnItemLongClickListener interface // OnItemLongClickListener interface
public boolean onItemLongClick( AdapterView<?> parent, View view, public boolean onItemLongClick( AdapterView<?> parent, View view,
int position, long id ) { int position, long id ) {
boolean success = view instanceof SelectableItem.LongClickHandler; boolean success = ! XWApp.CONTEXT_MENUS_ENABLED
&& view instanceof SelectableItem.LongClickHandler;
if ( success ) { if ( success ) {
((SelectableItem.LongClickHandler)view).longClicked(); ((SelectableItem.LongClickHandler)view).longClicked();
} }
@ -1044,7 +1080,7 @@ public class GamesListDelegate extends ListDelegateBase
if ( DBUtils.ROWIDS_ALL == rowid ) { // all changed if ( DBUtils.ROWIDS_ALL == rowid ) { // all changed
mkListAdapter(); mkListAdapter();
} else { } else {
m_adapter.reloadGame( rowid ); reloadGame( rowid );
} }
break; break;
case GAME_CREATED: case GAME_CREATED:
@ -1173,11 +1209,13 @@ public class GamesListDelegate extends ListDelegateBase
long curID = (Long)params[0]; long curID = (Long)params[0];
long newid = GameUtils.dupeGame( m_activity, curID ); long newid = GameUtils.dupeGame( m_activity, curID );
m_selGames.add( newid ); m_selGames.add( newid );
if ( null != m_adapter ) { reloadGame( newid );
m_adapter.reloadGame( newid );
}
break; break;
case SET_HIDE_NEWGAME_BUTTONS:
XWPrefs.setHideNewgameButtons(m_activity, true);
setupButtons();
// FALLTHRU
case NEW_GAME_PRESSED: case NEW_GAME_PRESSED:
handleNewGame( m_nextIsSolo ); handleNewGame( m_nextIsSolo );
break; break;
@ -1199,10 +1237,6 @@ public class GamesListDelegate extends ListDelegateBase
case CLEAR_SELS: case CLEAR_SELS:
clearSelections(); clearSelections();
break; break;
case SET_HIDE_NEWGAME_BUTTONS:
XWPrefs.setHideNewgameButtons( m_activity, true );
setupButtons();
break;
case DWNLD_LOC_DICT: case DWNLD_LOC_DICT:
String lang = (String)params[0]; String lang = (String)params[0];
String name = (String)params[1]; String name = (String)params[1];
@ -1228,19 +1262,28 @@ public class GamesListDelegate extends ListDelegateBase
}; };
DwnldDelegate.downloadDictInBack( m_activity, lang, name, lstnr ); DwnldDelegate.downloadDictInBack( m_activity, lang, name, lstnr );
break; break;
case NEW_GAME_DFLT_NAME:
m_newGameParams = params;
askDefaultName();
break;
default: default:
Assert.fail(); Assert.fail();
} }
} else if ( AlertDialog.BUTTON_NEGATIVE == which ) {
if ( Action.NEW_GAME_DFLT_NAME == action ) {
m_newGameParams = params;
makeThenLaunchOrConfigure();
}
} }
} }
@Override @Override
protected void onActivityResult( int requestCode, int resultCode, protected void onActivityResult( RequestCode requestCode, int resultCode,
Intent data ) Intent data )
{ {
boolean cancelled = Activity.RESULT_CANCELED == resultCode; boolean cancelled = Activity.RESULT_CANCELED == resultCode;
switch ( requestCode ) { switch ( requestCode ) {
case REQUEST_LANG: case REQUEST_LANG_GL:
if ( !cancelled ) { if ( !cancelled ) {
DbgUtils.logf( "lang need met" ); DbgUtils.logf( "lang need met" );
if ( checkWarnNoDict( m_missingDictRowId ) ) { if ( checkWarnNoDict( m_missingDictRowId ) ) {
@ -1318,11 +1361,19 @@ public class GamesListDelegate extends ListDelegateBase
.contains( XWPrefs.getDefaultNewGameGroup( m_activity ) ); .contains( XWPrefs.getDefaultNewGameGroup( m_activity ) );
Utils.setItemVisible( menu, R.id.games_group_default, enable ); Utils.setItemVisible( menu, R.id.games_group_default, enable );
// Rematch supported if there's one game selected
enable = 1 == nGamesSelected;
if ( enable ) {
enable = BoardDelegate.rematchSupported( m_activity,
getSelRowIDs()[0] );
}
Utils.setItemVisible( menu, R.id.games_game_rematch, enable );
// Move up/down enabled for groups if not the top-most or bottommost // Move up/down enabled for groups if not the top-most or bottommost
// selected // selected
enable = 1 == nGroupsSelected; enable = 1 == nGroupsSelected;
Utils.setItemVisible( menu, R.id.games_group_moveup, Utils.setItemVisible( menu, R.id.games_group_moveup,
enable && 0 <= selGroupPos ); enable && 0 < selGroupPos );
Utils.setItemVisible( menu, R.id.games_group_movedown, enable Utils.setItemVisible( menu, R.id.games_group_movedown, enable
&& (selGroupPos + 1) < groupCount ); && (selGroupPos + 1) < groupCount );
@ -1365,10 +1416,10 @@ public class GamesListDelegate extends ListDelegateBase
{ {
Assert.assertTrue( m_menuPrepared ); Assert.assertTrue( m_menuPrepared );
String msg;
int itemID = item.getItemId(); int itemID = item.getItemId();
boolean handled = true; boolean handled = true;
boolean changeContent = false; boolean changeContent = false;
boolean dropSels = false;
int groupPos = getSelGroupPos(); int groupPos = getSelGroupPos();
long groupID = DBUtils.GROUPID_UNSPEC; long groupID = DBUtils.GROUPID_UNSPEC;
if ( 0 <= groupPos ) { if ( 0 <= groupPos ) {
@ -1400,10 +1451,6 @@ public class GamesListDelegate extends ListDelegateBase
showDialog( DlgID.NEW_GROUP ); showDialog( DlgID.NEW_GROUP );
break; break;
case R.id.games_game_config:
GameUtils.doConfig( m_activity, selRowIDs[0], GameConfigActivity.class );
break;
case R.id.games_menu_dicts: case R.id.games_menu_dicts:
DictsActivity.start( m_activity ); DictsActivity.start( m_activity );
break; break;
@ -1455,102 +1502,11 @@ public class GamesListDelegate extends ListDelegateBase
showToast( R.string.db_store_done ); showToast( R.string.db_store_done );
break; break;
// Game menus: one or more games selected
case R.id.games_game_delete:
String msg = getQuantityString( R.plurals.confirm_seldeletes_fmt,
selRowIDs.length, selRowIDs.length );
showConfirmThen( msg, R.string.button_delete,
Action.DELETE_GAMES, selRowIDs );
break;
case R.id.games_game_move:
m_rowids = selRowIDs;
showDialog( DlgID.CHANGE_GROUP );
break;
case R.id.games_game_new_from:
dropSels = true; // will select the new game instead
showNotAgainDlgThen( R.string.not_again_newfrom,
R.string.key_notagain_newfrom,
Action.NEW_FROM, selRowIDs[0] );
break;
case R.id.games_game_copy:
final GameSummary smry = DBUtils.getSummary( m_activity, selRowIDs[0] );
if ( smry.inRelayGame() ) {
showOKOnlyDialog( R.string.no_copy_network );
} else {
dropSels = true; // will select the new game instead
post( new Runnable() {
public void run() {
Activity self = m_activity;
byte[] stream =
GameUtils.savedGame( self, selRowIDs[0] );
long groupID = XWPrefs
.getDefaultNewGameGroup( self );
GameLock lock =
GameUtils.saveNewGame( self, stream, groupID );
DBUtils.saveSummary( self, lock, smry );
m_selGames.add( lock.getRowid() );
lock.unlock();
mkListAdapter();
}
});
}
break;
case R.id.games_game_reset:
doConfirmReset( selRowIDs );
break;
case R.id.games_game_rename:
m_rowid = selRowIDs[0];
showDialog( DlgID.RENAME_GAME );
break;
// Group menus
case R.id.games_group_delete:
long dftGroup = XWPrefs.getDefaultNewGameGroup( m_activity );
if ( m_selGroupIDs.contains( dftGroup ) ) {
msg = getString( R.string.cannot_delete_default_group_fmt,
m_adapter.groupName( dftGroup ) );
showOKOnlyDialog( msg );
} else {
long[] groupIDs = getSelGroupIDs();
Assert.assertTrue( 0 < groupIDs.length );
msg = getQuantityString( R.plurals.groups_confirm_del_fmt,
groupIDs.length, groupIDs.length );
int nGames = 0;
for ( long tmp : groupIDs ) {
nGames += m_adapter.getChildrenCount( tmp );
}
if ( 0 < nGames ) {
msg += getQuantityString( R.plurals.groups_confirm_del_games_fmt,
nGames, nGames );
}
showConfirmThen( msg, Action.DELETE_GROUPS, groupIDs );
}
break;
case R.id.games_group_default:
XWPrefs.setDefaultNewGameGroup( m_activity, groupID );
break;
case R.id.games_group_rename:
m_groupid = groupID;
showDialog( DlgID.RENAME_GROUP );
break;
case R.id.games_group_moveup:
moveGroup( groupID, true );
break;
case R.id.games_group_movedown:
moveGroup( groupID, false );
break;
default: default:
handled = false; handled = handleSelGamesItem( itemID, selRowIDs )
|| handleSelGroupsItem( itemID, getSelGroupIDs() );
} }
if ( dropSels ) {
clearSelections();
}
if ( changeContent ) { if ( changeContent ) {
mkListAdapter(); mkListAdapter();
} }
@ -1558,6 +1514,72 @@ public class GamesListDelegate extends ListDelegateBase
return handled;// || super.onOptionsItemSelected( item ); return handled;// || super.onOptionsItemSelected( item );
} }
public void onCreateContextMenu( ContextMenu menu, View view,
ContextMenuInfo menuInfo )
{
super.onCreateContextMenu( menu, view, menuInfo );
int id = 0;
boolean selected = false;
long gameRowID = 0;
AdapterView.AdapterContextMenuInfo info
= (AdapterView.AdapterContextMenuInfo)menuInfo;
View targetView = info.targetView;
DbgUtils.logf( "onCreateContextMenu(t=%s)",
targetView.getClass().getName() );
if ( targetView instanceof GameListItem ) {
id = R.menu.games_list_game_menu;
gameRowID = ((GameListItem)targetView).getRowID();
selected = m_selGames.contains( gameRowID );
} else if ( targetView instanceof GameListGroup ) {
id = R.menu.games_list_group_menu;
long groupID = ((GameListGroup)targetView).getGroupID();
selected = m_selGroupIDs.contains( groupID );
} else {
Assert.fail();
}
if ( 0 != id ) {
m_activity.getMenuInflater().inflate( id, menu );
int hideId = selected
? R.id.games_game_select : R.id.games_game_deselect;
Utils.setItemVisible( menu, hideId, false );
if ( 0 != gameRowID ) {
boolean enable = BoardDelegate.rematchSupported( m_activity,
gameRowID );
Utils.setItemVisible( menu, R.id.games_game_rematch, enable );
}
}
}
public boolean onContextItemSelected( MenuItem item )
{
boolean handled = true;
AdapterView.AdapterContextMenuInfo info
= (AdapterView.AdapterContextMenuInfo)item.getMenuInfo();
View targetView = info.targetView;
int itemID = item.getItemId();
if ( ! handleToggleItem( itemID, targetView ) ) {
long[] selIds = new long[1];
if ( targetView instanceof GameListItem ) {
selIds[0] = ((GameListItem)targetView).getRowID();
handled = handleSelGamesItem( itemID, selIds );
} else if ( targetView instanceof GameListGroup ) {
selIds[0] = ((GameListGroup)targetView).getGroupID();
handled = handleSelGroupsItem( itemID, selIds );
} else {
Assert.fail();
}
}
return handled || super.onContextItemSelected( item );
}
////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////
// DwnldActivity.DownloadFinishedListener interface // DwnldActivity.DownloadFinishedListener interface
////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////
@ -1603,6 +1625,154 @@ public class GamesListDelegate extends ListDelegateBase
} }
} }
private void reloadGame( long rowID )
{
if ( null != m_adapter ) {
m_adapter.reloadGame( rowID );
}
}
private boolean handleToggleItem( int itemID, View target )
{
boolean handled;
switch( itemID ) {
case R.id.games_game_select:
case R.id.games_game_deselect:
SelectableItem.LongClickHandler toggled
= (SelectableItem.LongClickHandler)target;
toggled.longClicked();
handled = true;
break;
default:
handled = false;
}
return handled;
}
private boolean handleSelGamesItem( int itemID, final long[] selRowIDs )
{
boolean handled = true;
boolean dropSels = false;
switch( itemID ) {
case R.id.games_game_delete:
String msg = getQuantityString( R.plurals.confirm_seldeletes_fmt,
selRowIDs.length, selRowIDs.length );
showConfirmThen( msg, R.string.button_delete,
Action.DELETE_GAMES, selRowIDs );
break;
case R.id.games_game_rematch:
Assert.assertTrue( 1 == selRowIDs.length );
BoardDelegate.setupRematchFor( m_activity, selRowIDs[0] );
break;
case R.id.games_game_config:
GameUtils.doConfig( m_activity, selRowIDs[0], GameConfigActivity.class );
break;
case R.id.games_game_move:
m_rowids = selRowIDs;
showDialog( DlgID.CHANGE_GROUP );
break;
case R.id.games_game_new_from:
dropSels = true; // will select the new game instead
showNotAgainDlgThen( R.string.not_again_newfrom,
R.string.key_notagain_newfrom,
Action.NEW_FROM, selRowIDs[0] );
break;
case R.id.games_game_copy:
final GameSummary smry = DBUtils.getSummary( m_activity, selRowIDs[0] );
if ( smry.inRelayGame() ) {
showOKOnlyDialog( R.string.no_copy_network );
} else {
dropSels = true; // will select the new game instead
post( new Runnable() {
public void run() {
Activity self = m_activity;
byte[] stream =
GameUtils.savedGame( self, selRowIDs[0] );
long groupID = XWPrefs
.getDefaultNewGameGroup( self );
GameLock lock =
GameUtils.saveNewGame( self, stream, groupID );
DBUtils.saveSummary( self, lock, smry );
m_selGames.add( lock.getRowid() );
lock.unlock();
mkListAdapter();
}
});
}
break;
case R.id.games_game_reset:
doConfirmReset( selRowIDs );
break;
case R.id.games_game_rename:
m_rowid = selRowIDs[0];
showDialog( DlgID.RENAME_GAME );
break;
default:
handled = false;
}
if ( dropSels ) {
clearSelections();
}
return handled;
}
private boolean handleSelGroupsItem( int itemID, long[] groupIDs )
{
boolean handled = true;
String msg;
Assert.assertTrue( 0 < groupIDs.length );
long groupID = groupIDs[0];
switch( itemID ) {
case R.id.games_group_delete:
long dftGroup = XWPrefs.getDefaultNewGameGroup( m_activity );
if ( m_selGroupIDs.contains( dftGroup ) ) {
msg = getString( R.string.cannot_delete_default_group_fmt,
m_adapter.groupName( dftGroup ) );
showOKOnlyDialog( msg );
} else {
Assert.assertTrue( 0 < groupIDs.length );
msg = getQuantityString( R.plurals.groups_confirm_del_fmt,
groupIDs.length, groupIDs.length );
int nGames = 0;
for ( long tmp : groupIDs ) {
nGames += m_adapter.getChildrenCount( tmp );
}
if ( 0 < nGames ) {
msg += getQuantityString( R.plurals.groups_confirm_del_games_fmt,
nGames, nGames );
}
showConfirmThen( msg, Action.DELETE_GROUPS, groupIDs );
}
break;
case R.id.games_group_default:
XWPrefs.setDefaultNewGameGroup( m_activity, groupID );
break;
case R.id.games_group_rename:
m_groupid = groupID;
showDialog( DlgID.RENAME_GROUP );
break;
case R.id.games_group_moveup:
moveGroup( groupID, true );
break;
case R.id.games_group_movedown:
moveGroup( groupID, false );
break;
default:
handled = false;
}
return handled;
}
private void setupButtons() private void setupButtons()
{ {
boolean hidden = XWPrefs.getHideNewgameButtons( m_activity ); boolean hidden = XWPrefs.getHideNewgameButtons( m_activity );
@ -1741,7 +1911,7 @@ public class GamesListDelegate extends ListDelegateBase
long[] rowids = DBUtils.getRowIDsFor( m_activity, relayID ); long[] rowids = DBUtils.getRowIDsFor( m_activity, relayID );
if ( null != rowids ) { if ( null != rowids ) {
for ( long rowid : rowids ) { for ( long rowid : rowids ) {
m_adapter.reloadGame( rowid ); reloadGame( rowid );
} }
} }
} }
@ -1848,33 +2018,44 @@ public class GamesListDelegate extends ListDelegateBase
// used to connect. // used to connect.
private void startRematch( Intent intent ) private void startRematch( Intent intent )
{ {
if ( XWApp.REMATCH_SUPPORTED ) { if ( XWApp.REMATCH_SUPPORTED
long rowid = intent.getLongExtra( REMATCH_ROWID_EXTRA, -1 ); && ( -1 != intent.getLongExtra( REMATCH_ROWID_EXTRA, -1 ) ) ) {
if ( -1 != rowid ) { m_rematchIntent = intent;
String btAddr = intent.getStringExtra( REMATCH_BTADDR_EXTRA ); showDialog( DlgID.GAMES_LIST_NAME_REMATCH );
String phone = intent.getStringExtra( REMATCH_PHONE_EXTRA );
String relayID = intent.getStringExtra( REMATCH_RELAYID_EXTRA );
long newid;
if ( null == btAddr && null == phone && null == relayID ) {
// this will juggle if the preference is set
newid = GameUtils.dupeGame( m_activity, rowid );
} else {
int bits = intent.getIntExtra( REMATCH_ADDRS_EXTRA, -1 );
CommsConnTypeSet addrs = new CommsConnTypeSet( bits );
long groupID = DBUtils.getGroupForGame( m_activity, rowid );
String gameName = "rematch"; // FIX ME :-)
newid = GameUtils.makeNewMultiGame( m_activity, groupID,
addrs, gameName );
DBUtils.addRematchInfo( m_activity, newid, btAddr, phone,
relayID );
}
launchGame( newid );
}
} }
} }
private void startRematchWithName( EditText edit )
{
String gameName = edit.getText().toString();
if ( null != gameName && 0 < gameName.length() ) {
Intent intent = m_rematchIntent;
long srcRowID = intent.getLongExtra( REMATCH_ROWID_EXTRA, -1 );
String btAddr = intent.getStringExtra( REMATCH_BTADDR_EXTRA );
String phone = intent.getStringExtra( REMATCH_PHONE_EXTRA );
String relayID = intent.getStringExtra( REMATCH_RELAYID_EXTRA );
String dict = intent.getStringExtra( REMATCH_DICT_EXTRA );
int lang = intent.getIntExtra( REMATCH_LANG_EXTRA, -1 );
int bits = intent.getIntExtra( REMATCH_ADDRS_EXTRA, -1 );
CommsConnTypeSet addrs = new CommsConnTypeSet( bits );
long newid;
if ( null == btAddr && null == phone && null == relayID ) {
newid = GameUtils.dupeGame( m_activity, srcRowID );
DBUtils.setName( m_activity, newid, gameName );
} else {
long groupID = DBUtils.getGroupForGame( m_activity, srcRowID );
newid = GameUtils.makeNewMultiGame( m_activity, groupID,
dict, lang,
addrs, gameName );
DBUtils.addRematchInfo( m_activity, newid, btAddr, phone,
relayID );
}
launchGame( newid );
}
m_rematchIntent = null;
}
private void tryAlert( Intent intent ) private void tryAlert( Intent intent )
{ {
String msg = intent.getStringExtra( ALERT_MSG ); String msg = intent.getStringExtra( ALERT_MSG );
@ -1894,16 +2075,11 @@ public class GamesListDelegate extends ListDelegateBase
} }
} }
private boolean askDefaultNameIf() private void askDefaultName()
{ {
boolean showing = String name = CommonPrefs.getDefaultPlayerName( m_activity, 0, true );
null == CommonPrefs.getDefaultPlayerName( m_activity, 0, false ); CommonPrefs.setDefaultPlayerName( m_activity, name );
if ( showing ) { showDialog( DlgID.GET_NAME );
String name = CommonPrefs.getDefaultPlayerName( m_activity, 0, true );
CommonPrefs.setDefaultPlayerName( m_activity, name );
showDialog( DlgID.GET_NAME );
}
return showing;
} }
private void getDictForLangIf() private void getDictForLangIf()
@ -2185,7 +2361,9 @@ public class GamesListDelegate extends ListDelegateBase
m_adapter = new GameListAdapter(); m_adapter = new GameListAdapter();
setListAdapterKeepScroll( m_adapter ); setListAdapterKeepScroll( m_adapter );
// ListView listview = getListView(); ListView listView = getListView();
m_activity.registerForContextMenu( listView );
// String field = CommonPrefs.getSummaryField( m_activity ); // String field = CommonPrefs.getSummaryField( m_activity );
// long[] positions = XWPrefs.getGroupPositions( m_activity ); // long[] positions = XWPrefs.getGroupPositions( m_activity );
// GameListAdapter adapter = // GameListAdapter adapter =
@ -2196,26 +2374,80 @@ public class GamesListDelegate extends ListDelegateBase
// return adapter; // return adapter;
} }
private void makeThenLaunchOrConfigure( EditText edit, boolean doConfigure ) // Returns true if user has what looks like a default name and has not
// said he wants us to stop bugging him about it.
private boolean askingChangeName( EditText edit, boolean doConfigure )
{ {
String name = edit.getText().toString(); boolean asking = false;
long rowID; boolean skipAsk = XWPrefs
long groupID = 1 == m_selGroupIDs.size() .getPrefsBoolean( m_activity, R.string.key_notagain_dfltname,
? m_selGroupIDs.iterator().next() : DBUtils.GROUPID_UNSPEC; false );
if ( m_nextIsSolo ) { if ( ! skipAsk ) {
rowID = GameUtils.saveNew( m_activity, String name1 = CommonPrefs.getDefaultPlayerName( m_activity, 0,
new CurGameInfo( m_activity ), false );
groupID, name ); String name2 = CommonPrefs.getDefaultOriginalPlayerName( m_activity, 0 );
} else { if ( null == name1 || name1.equals( name2 ) ) {
rowID = GameUtils.makeNewMultiGame( m_activity, groupID, name ); asking = true;
}
if ( doConfigure ) { String msg = LocUtils
// configure it .getString( m_activity, R.string.not_again_dfltname_fmt,
GameConfigDelegate.editForResult( m_activity, CONFIG_GAME, rowID ); name2 );
} else {
// launch it Runnable onChecked = new Runnable() {
GameUtils.launchGame( m_activity, rowID ); public void run() {
XWPrefs
.setPrefsBoolean( m_activity,
R.string.key_notagain_dfltname,
true );
}
};
showConfirmThen( onChecked, msg, android.R.string.ok,
R.string.button_later, Action.NEW_GAME_DFLT_NAME,
edit, doConfigure );
}
}
return asking;
}
private boolean makeThenLaunchOrConfigure()
{
boolean handled = null != m_newGameParams;
if ( handled ) {
EditText edit = (EditText)m_newGameParams[0];
boolean doConfigure = (Boolean)m_newGameParams[1];
m_newGameParams = null;
makeThenLaunchOrConfigure( edit, doConfigure, true );
}
return handled;
}
private void makeThenLaunchOrConfigure( EditText edit, boolean doConfigure,
boolean skipAsk )
{
if ( skipAsk || !askingChangeName( edit, doConfigure ) ) {
String name = edit.getText().toString();
long rowID;
long groupID = 1 == m_selGroupIDs.size()
? m_selGroupIDs.iterator().next() : DBUtils.GROUPID_UNSPEC;
// Ideally we'd check here whether user has set player name.
if ( m_nextIsSolo ) {
rowID = GameUtils.saveNew( m_activity,
new CurGameInfo( m_activity ),
groupID, name );
} else {
rowID = GameUtils.makeNewMultiGame( m_activity, groupID, name );
}
if ( doConfigure ) {
// configure it
GameConfigDelegate.editForResult( m_activity, RequestCode
.CONFIG_GAME, rowID );
} else {
// launch it
GameUtils.launchGame( m_activity, rowID );
}
} }
} }
@ -2255,24 +2487,30 @@ public class GamesListDelegate extends ListDelegateBase
} }
public static Intent makeRematchIntent( Context context, long rowid, public static Intent makeRematchIntent( Context context, long rowid,
String dict, int lang,
CommsConnTypeSet addrTypes, CommsConnTypeSet addrTypes,
String btAddr, String phone, String btAddr, String phone,
String relayID ) String relayID, String newName )
{ {
Intent intent = null; Intent intent = null;
if ( XWApp.REMATCH_SUPPORTED ) { if ( XWApp.REMATCH_SUPPORTED ) {
DbgUtils.logf( "makeRematchIntent(btAddr=%s; phone=%s)", btAddr, phone );
intent = makeSelfIntent( context ); intent = makeSelfIntent( context );
intent.putExtra( REMATCH_ROWID_EXTRA, rowid ); intent.putExtra( REMATCH_ROWID_EXTRA, rowid );
intent.putExtra( REMATCH_ADDRS_EXTRA, addrTypes.toInt() ); intent.putExtra( REMATCH_DICT_EXTRA, dict );
if ( null != btAddr ) { intent.putExtra( REMATCH_LANG_EXTRA, lang );
intent.putExtra( REMATCH_BTADDR_EXTRA, btAddr ); intent.putExtra( REMATCH_NEWNAME_EXTRA, newName );
}
if ( null != phone ) { if ( null != addrTypes ) {
intent.putExtra( REMATCH_PHONE_EXTRA, phone ); intent.putExtra( REMATCH_ADDRS_EXTRA, addrTypes.toInt() ); // here
} if ( null != btAddr ) {
if ( null != relayID ) { intent.putExtra( REMATCH_BTADDR_EXTRA, btAddr );
intent.putExtra( REMATCH_RELAYID_EXTRA, relayID ); }
if ( null != phone ) {
intent.putExtra( REMATCH_PHONE_EXTRA, phone );
}
if ( null != relayID ) {
intent.putExtra( REMATCH_RELAYID_EXTRA, relayID );
}
} }
} }
return intent; return intent;

View file

@ -20,11 +20,10 @@
package org.eehouse.android.xw4; package org.eehouse.android.xw4;
import android.app.ListActivity;
import android.os.Bundle; import android.os.Bundle;
import android.view.Window; import android.view.Window;
public abstract class InviteActivity extends XWListActivity { public abstract class InviteActivity extends XWActivity {
@Override @Override
protected void onCreate( Bundle savedInstanceState ) protected void onCreate( Bundle savedInstanceState )

View file

@ -22,18 +22,20 @@ package org.eehouse.android.xw4;
import android.app.Activity; import android.app.Activity;
import android.app.Dialog; import android.app.Dialog;
import android.app.ListActivity;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.os.Bundle; import android.os.Bundle;
import android.view.View; import android.view.View;
import android.widget.Button; import android.widget.Button;
import android.widget.ListView;
import android.widget.TextView; import android.widget.TextView;
import android.view.ViewGroup;
import junit.framework.Assert; import junit.framework.Assert;
abstract class InviteDelegate extends ListDelegateBase abstract class InviteDelegate extends ListDelegateBase
implements View.OnClickListener { implements View.OnClickListener,
ViewGroup.OnHierarchyChangeListener {
public static final String DEVS = "DEVS"; public static final String DEVS = "DEVS";
public static final String COUNTS = "COUNTS"; public static final String COUNTS = "COUNTS";
@ -44,8 +46,10 @@ abstract class InviteDelegate extends ListDelegateBase
protected Button m_rescanButton; protected Button m_rescanButton;
protected Button m_clearButton; protected Button m_clearButton;
private Activity m_activity; private Activity m_activity;
private ListView m_lv;
private View m_ev;
public InviteDelegate( ListDelegator delegator, Bundle savedInstanceState, public InviteDelegate( Delegator delegator, Bundle savedInstanceState,
int layoutID ) int layoutID )
{ {
super( delegator, savedInstanceState, layoutID, R.menu.empty ); super( delegator, savedInstanceState, layoutID, R.menu.empty );
@ -67,9 +71,19 @@ abstract class InviteDelegate extends ListDelegateBase
TextView descView = (TextView)findViewById( desc_id ); TextView descView = (TextView)findViewById( desc_id );
descView.setText( descTxt ); descView.setText( descTxt );
m_lv = (ListView)findViewById( android.R.id.list );
m_ev = findViewById( android.R.id.empty );
if ( null != m_lv && null != m_ev ) {
m_lv.setOnHierarchyChangeListener( this );
showEmptyIfEmpty();
}
tryEnable(); tryEnable();
} }
////////////////////////////////////////
// View.OnClickListener
////////////////////////////////////////
public void onClick( View view ) public void onClick( View view )
{ {
if ( m_okButton == view ) { if ( m_okButton == view ) {
@ -88,6 +102,24 @@ abstract class InviteDelegate extends ListDelegateBase
} }
} }
////////////////////////////////////////
// ViewGroup.OnHierarchyChangeListener
////////////////////////////////////////
public void onChildViewAdded( View parent, View child )
{
showEmptyIfEmpty();
}
public void onChildViewRemoved( View parent, View child )
{
showEmptyIfEmpty();
}
private void showEmptyIfEmpty()
{
m_ev.setVisibility( 0 == m_lv.getChildCount()
? View.VISIBLE : View.GONE );
}
abstract void tryEnable() ; abstract void tryEnable() ;
abstract void listSelected( String[][] devsP, int[][] countsP ); abstract void listSelected( String[][] devsP, int[][] countsP );
abstract void scan(); abstract void scan();

View file

@ -28,15 +28,15 @@ import android.widget.ListView;
public class ListDelegateBase extends DelegateBase { public class ListDelegateBase extends DelegateBase {
private Activity m_activity; private Activity m_activity;
private ListDelegator m_delegator; private Delegator m_delegator;
protected ListDelegateBase( ListDelegator delegator, Bundle savedInstanceState, protected ListDelegateBase( Delegator delegator, Bundle savedInstanceState,
int layoutID ) int layoutID )
{ {
this( delegator, savedInstanceState, layoutID, R.menu.empty ); this( delegator, savedInstanceState, layoutID, R.menu.empty );
} }
protected ListDelegateBase( ListDelegator delegator, Bundle savedInstanceState, protected ListDelegateBase( Delegator delegator, Bundle savedInstanceState,
int layoutID, int menuID ) int layoutID, int menuID )
{ {
super( delegator, savedInstanceState, layoutID, menuID ); super( delegator, savedInstanceState, layoutID, menuID );

View file

@ -21,8 +21,8 @@
package org.eehouse.android.xw4; package org.eehouse.android.xw4;
import android.content.Context; import android.content.Context;
import java.util.HashMap; import java.util.HashSet;
import java.util.ArrayList; import java.util.Set;
import junit.framework.Assert; import junit.framework.Assert;
@ -32,7 +32,9 @@ import org.eehouse.android.xw4.jni.CommsAddrRec.CommsConnType;
public class MultiMsgSink implements TransportProcs { public class MultiMsgSink implements TransportProcs {
private long m_rowid; private long m_rowid;
private Context m_context; private Context m_context;
private int m_nSent = 0; // Use set to count so message sent over both BT and Relay is counted only
// once.
private Set<String> m_sentSet = new HashSet<String>();
public MultiMsgSink( Context context, long rowid ) public MultiMsgSink( Context context, long rowid )
{ {
@ -60,7 +62,7 @@ public class MultiMsgSink implements TransportProcs {
public int sendViaBluetooth( byte[] buf, int gameID, CommsAddrRec addr ) public int sendViaBluetooth( byte[] buf, int gameID, CommsAddrRec addr )
{ {
return BTService.enqueueFor( m_context, buf, addr.bt_btAddr, gameID ); return BTService.enqueueFor( m_context, buf, addr, gameID );
} }
public int sendViaSMS( byte[] buf, int gameID, CommsAddrRec addr ) public int sendViaSMS( byte[] buf, int gameID, CommsAddrRec addr )
@ -70,15 +72,15 @@ public class MultiMsgSink implements TransportProcs {
public int numSent() public int numSent()
{ {
return m_nSent; return m_sentSet.size();
} }
/***** TransportProcs interface *****/ /***** TransportProcs interface *****/
public int getFlags() { return COMMS_XPORT_FLAGS_HASNOCONN; } public int getFlags() { return COMMS_XPORT_FLAGS_HASNOCONN; }
public int transportSend( byte[] buf, CommsAddrRec addr, CommsConnType typ, public int transportSend( byte[] buf, String msgNo, CommsAddrRec addr,
int gameID ) CommsConnType typ, int gameID )
{ {
int nSent = -1; int nSent = -1;
switch ( typ ) { switch ( typ ) {
@ -97,6 +99,10 @@ public class MultiMsgSink implements TransportProcs {
} }
DbgUtils.logf( "MultiMsgSink.transportSend(): sent %d via %s", DbgUtils.logf( "MultiMsgSink.transportSend(): sent %d via %s",
nSent, typ.toString() ); nSent, typ.toString() );
if ( 0 < nSent ) {
DbgUtils.logdf( "MultiMsgSink.transportSend: adding %s", msgNo );
m_sentSet.add( msgNo );
}
return nSent; return nSent;
} }
@ -114,11 +120,16 @@ public class MultiMsgSink implements TransportProcs {
{ {
} }
public boolean relayNoConnProc( byte[] buf, String relayID ) public boolean relayNoConnProc( byte[] buf, String msgNo, String relayID )
{ {
// Assert.fail(); // Assert.fail();
int nSent = RelayService.sendNoConnPacket( m_context, getRowID(), int nSent = RelayService.sendNoConnPacket( m_context, getRowID(),
relayID, buf ); relayID, buf );
return buf.length == nSent; boolean success = buf.length == nSent;
if ( success ) {
DbgUtils.logdf( "MultiMsgSink.relayNoConnProc: adding %s", msgNo );
m_sentSet.add( msgNo );
}
return success;
} }
} }

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