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. -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.eehouse.android.xw4dbg"
android:versionCode="91"
android:versionCode="95"
android:versionName="@string/app_version"
>
@ -70,6 +70,7 @@
<application android:icon="@drawable/icon48x48"
android:label="@string/app_name"
android:name=".XWApp"
android:debuggable="true"
>
<activity android:name="GamesListActivity"
@ -106,6 +107,11 @@
android:configChanges="keyboardHidden|orientation|screenSize"
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"
android:screenOrientation="sensor"

View file

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

View file

@ -1,6 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<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.
It contains the path to the SDK. It should *NOT* be checked into
Version Control Systems. -->
@ -52,7 +55,7 @@
<!-- extension targets. Uncomment the ones where you want to do custom work
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="APP_NAME" value="CrossDbg"/>

View file

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

View file

@ -1,10 +1,11 @@
board_menu.xml
chat_menu.xml
dicts_item_menu.xml
games_list_item_menu.xml
games_list_menu.xml
dicts_menu.xml
studylist.xml
loc_menu.xml
empty.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

View file

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

View file

@ -1 +1,2 @@
/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
/LookupAlert.java
ABUtils.java
@ -113,11 +112,16 @@ GroupStateListener.java
ListGroup.java
ConnViaViewLayout.java
Delegator.java
DevID.java
HeaderWithExpander.java
LangListPreference.java
ListDelegator.java
NagTurnReceiver.java
NotAgainView.java
OnBootReceiver.java
RelayInviteActivity.java
RelayInviteDelegate.java
XWConnAddrPreference.java
XWDevIDPreference.java
XWExpListAdapter.java
RequestCode.java

View file

@ -1,9 +1,19 @@
*.apk
.DS_Store
.gradle
/.idea/libraries
/.idea/workspace.xml
/build
/captures
/libs-debug
/libs-release
/local.properties
/obj-debug
/obj-release
ant_out.txt
local.properties
bin
gen
libs
proguard.cfg
local.properties
obj
proguard.cfg
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. -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.eehouse.android.xw4"
android:versionCode="91"
android:versionCode="95"
android:versionName="@string/app_version"
>
@ -37,6 +37,7 @@
android:largeScreens="true"
android:xlargeScreens="true"
/>
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
@ -102,6 +103,11 @@
android:configChanges="keyboardHidden|orientation|screenSize"
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"
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>
</head>
<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
there, and to catch up Catalan and French.</p>
<p>This is the second of two releases that together fix stalling
issues in network games.</p>
<div id="survey">
<p>Please <a href="https://www.surveymonkey.com/s/GX3XLHR">take
@ -26,23 +26,8 @@
<h3>New with this release</h3>
<ul>
<li>Complete and up-to-date translations into Dutch, French and
Catalan. What language is next?</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>
<li>Fix to send correctly calculated identifier for game
state.</li>
</ul>
<p>(The full changelog
@ -51,8 +36,9 @@
<h3>Next up</h3>
<ul>
<li>Offer &quot;Rematch&quot; when game&apos;s over (Easy via
SMS and Bluetooth; harder via the internet/relay)</li>
<li>Look into supporting play via peer-to-peer wifi</li>
SMS and Bluetooth; harder via the internet/relay)</li>
<li>Take advantage of Marshmallow's new permissions model (where
the app only asks for them when it needs them.)
</ul>
<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"?>
<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.
It contains the path to the SDK. It should *NOT* be checked into
Version Control Systems. -->
@ -52,7 +55,7 @@
<!-- extension targets. Uncomment the ones where you want to do custom work
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="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_DEVID \
-DCOMMON_LAYOUT \
-DNATIVE_NLI \
-DCOMMS_VERSION=1 \
-DINITIAL_CLIENT_VERS=${INITIAL_CLIENT_VERS} \
-DVARIANT_${VARIANT} \
@ -84,7 +85,7 @@ COMMON_SRC_FILES += \
$(COMMON_PATH)/memstream.c \
$(COMMON_PATH)/movestak.c \
$(COMMON_PATH)/dbgutil.c \
$(COMMON_PATH)/nli.c \
LOCAL_CFLAGS+=$(LOCAL_C_INCLUDES) $(LOCAL_DEFINES) -Wall -std=c99
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.
*
* 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)
env, jlangname );
XP_U32 numEdges;
XP_U32 numEdges = 0;
XP_Bool parses = parseDict( anddict, (XP_U8*)anddict->bytes,
bytesSize, &numEdges );
if ( !parses || (check && !checkSanity( &anddict->super,

View file

@ -78,12 +78,14 @@ error error error
int
getInt( JNIEnv* env, jobject obj, const char* name )
{
// XP_LOGF( "%s(name=%s)", __func__, name );
jclass cls = (*env)->GetObjectClass( env, obj );
XP_ASSERT( !!cls );
jfieldID fid = (*env)->GetFieldID( env, cls, name, "I");
XP_ASSERT( !!fid );
int result = (*env)->GetIntField( env, obj, fid );
deleteLocalRef( env, cls );
// XP_LOGF( "%s(name=%s) => %d", __func__, name, result );
return result;
}
@ -113,12 +115,14 @@ getInts( JNIEnv* env, void* cobj, jobject jobj, const SetInfo* sis, XP_U16 nSis
void
setInt( JNIEnv* env, jobject obj, const char* name, int value )
{
// XP_LOGF( "%s(name=%s)", __func__, name );
jclass cls = (*env)->GetObjectClass( env, obj );
XP_ASSERT( !!cls );
jfieldID fid = (*env)->GetFieldID( env, cls, name, "I");
XP_ASSERT( !!fid );
(*env)->SetIntField( env, obj, fid, value );
deleteLocalRef( env, cls );
// XP_LOGF( "%s(name=%s) DONE", __func__, name );
}
void
@ -178,6 +182,7 @@ setBools( JNIEnv* env, jobject jobj, void* cobj, const SetInfo* sis, XP_U16 nSis
bool
setString( JNIEnv* env, jobject obj, const char* name, const XP_UCHAR* value )
{
// XP_LOGF( "%s(%s)", __func__, name );
bool success = false;
jclass cls = (*env)->GetObjectClass( env, obj );
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;
}
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
getString( JNIEnv* env, jobject obj, const char* name, XP_UCHAR* buf,
int bufLen )
{
// XP_LOGF( "%s(%s)", __func__, name );
jclass cls = (*env)->GetObjectClass( env, obj );
XP_ASSERT( !!cls );
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';
deleteLocalRef( env, cls );
// XP_LOGF( "%s(%s) DONE", __func__, name );
}
XP_UCHAR*
@ -403,9 +431,15 @@ getMethodID( JNIEnv* env, jobject obj, const char* proc, const char* sig )
XP_ASSERT( !!env );
jclass cls = (*env)->GetObjectClass( env, obj );
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 );
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 );
deleteLocalRef( env, cls );
@ -703,8 +737,46 @@ android_debugf( const char* format, ... )
# endif
, 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 */
/* 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 );
void getString( JNIEnv* env, jobject jlp, const char* name, XP_UCHAR* buf,
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 );
void setObject( JNIEnv* env, jobject obj, const char* name, const char* sig,
jobject val );

View file

@ -66,15 +66,16 @@ and_xport_getFlags( void* closure )
}
static XP_S16
and_xport_send( const XP_U8* buf, XP_U16 len, const CommsAddrRec* addr,
CommsConnType conType, XP_U32 gameID, void* closure )
and_xport_send( const XP_U8* buf, XP_U16 len, const XP_UCHAR* msgNo,
const CommsAddrRec* addr, CommsConnType conType,
XP_U32 gameID, void* closure )
{
jint result = -1;
LOG_FUNC();
AndTransportProcs* aprocs = (AndTransportProcs*)closure;
if ( NULL != aprocs->jxport ) {
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";
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 jConType =
intToJEnum(env, conType, PKG_PATH("jni/CommsAddrRec$CommsConnType"));
jstring jMsgNo = !!msgNo ? (*env)->NewStringUTF( env, msgNo ) : NULL;
result = (*env)->CallIntMethod( env, aprocs->jxport, mid,
jbytes, jaddr, jConType, gameID );
deleteLocalRefs( env, jaddr, jbytes, jConType, DELETE_NO_REF );
jbytes, jMsgNo, jaddr, jConType, gameID );
deleteLocalRefs( env, jaddr, jbytes, jMsgNo, jConType, DELETE_NO_REF );
}
LOG_RETURNF( "%d", result );
return result;
}
static void
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
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 )
{
jboolean result = false;
@ -134,14 +135,15 @@ and_xport_sendNoConn( const XP_U8* buf, XP_U16 len,
if ( NULL != aprocs && NULL != aprocs->jxport ) {
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,
"relayNoConnProc", sig );
jbyteArray jbytes = makeByteArray( env, len, (jbyte*)buf );
jstring jRelayID = (*env)->NewStringUTF( env, relayID );
jstring jMsgNo = !!msgNo ? (*env)->NewStringUTF( env, msgNo ) : NULL;
result = (*env)->CallBooleanMethod( env, aprocs->jxport, mid,
jbytes, jRelayID );
deleteLocalRefs( env, jbytes, jRelayID, DELETE_NO_REF );
jbytes, jMsgNo, jRelayID );
deleteLocalRefs( env, jbytes, jRelayID, jMsgNo, DELETE_NO_REF );
}
LOG_RETURNF( "%d", result );
return result;

View file

@ -32,6 +32,7 @@
#include "dictnry.h"
#include "dictiter.h"
#include "dictmgr.h"
#include "nli.h"
#include "utilwrapper.h"
#include "drawwrapper.h"
@ -283,6 +284,46 @@ makeGI( MPFORMAL JNIEnv* env, jobject jgi )
return gi;
} /* 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
setJGI( JNIEnv* env, jobject jgi, const CurGameInfo* gi )
{
@ -453,6 +494,59 @@ Java_org_eehouse_android_xw4_jni_XwJNI_gi_1from_1stream
#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
Java_org_eehouse_android_xw4_jni_XwJNI_comms_1getInitialAddr
( 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;
}
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
Java_org_eehouse_android_xw4_jni_XwJNI_comms_1getStats
( 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_height="wrap_content"
android:text="@string/game_locked"
android:checked="true"
android:visibility="gone"
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">
<!-- 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"
android:alphabeticShortcut="D"
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:showAsAction="ifRoom"
/>
<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"
android:icon="@drawable/content_edit"

View file

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

View file

@ -2,7 +2,6 @@
<!-- Resources in this file do not require localization -->
<resources>
<!-- prefs keys -->
<string name="key_color_tiles">key_color_tiles</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_skip_confirm">key_skip_confirm</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_peek_other">key_peek_other</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_bt_addrs">key_bt_addrs</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_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_nfc_toself">key_enable_nfc_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_download_path">key_download_path</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_invite_multi">key_invite_multi</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_sms">key_na_comms_sms</string>
@ -263,7 +267,7 @@
<!-- -->
<item>DIEC</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>:ca:</item>

View file

@ -163,7 +163,7 @@
<plurals name="confirm_seldeletes_fmt">
<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
the %1$d selected games? This action cannot be undone.</item>
</plurals>
@ -171,7 +171,7 @@
<!-- Text of confirmation dialog posted when list_item_reset menu
is selected -->
<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
information.)</item>
<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. -->
<string name="board_menu_game_resend">Resend messages</string>
<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>
</plurals>
@ -1191,6 +1191,7 @@
<string name="invite_choice_email">Email</string>
<string name="invite_choice_bt">Bluetooth</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="sms_or_email">Send invitation using SMS (texting) or -->
<!-- via email?</string> -->
@ -1719,12 +1720,11 @@
-->
<!-- Welcome dialog title -->
<string name="default_name_title">Welcome</string>
<string name="default_name_title">Default player name</string>
<!-- Welcome dialog text -->
<string name="default_name_message">Thanks for installing
Crosswords!\n\nFeel free to enter your name here. It will be used
when creating new games. (You can change it later in the \"New
game default\" section of Settings.)</string>
<string name="default_name_message">Please enter your name
here. It will be used when creating new games. (You can change it
later in the \"New game default\" section of Settings.)</string>
<!--
###########################################################
@ -1738,7 +1738,7 @@
<string name="about_vers_fmt">Crosswords for Android, Version %1$s,
rev %2$s, built on %3$s.</string>
<!-- 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
License.</string>
@ -1865,9 +1865,12 @@
<!-- -->
<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_relay_body">Tap to open the new game</string>
<!-- -->
<string name="bt_bad_proto_fmt">The version of Crosswords on
\"%1$s\" is incompatible with this one for play using
@ -1905,6 +1908,7 @@
<string name="bt_invite_title">Bluetooth Invitation</string>
<!-- Title of phone number picker during invitation to a game via SMS -->
<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>
@ -1921,12 +1925,12 @@
<!-- -->
<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="button_sms_add">Import contact</string>
<!-- -->
<string name="button_relay_add">Scan games</string>
<!-- -->
<plurals name="invite_sms_desc_fmt">
<item quantity="one">Please check the phone number you want to
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>
</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="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 +
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
phone number?</item>
<item quantity="other">Are you sure you want to delete the
%1$d checked phone numbers?</item>
</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>
<!-- -->
@ -2379,6 +2397,7 @@
<string name="new_game">New one-device 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
using default settings?\n\nOr would you like to configure it
@ -2419,6 +2438,8 @@
</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
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
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
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
%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="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="proxy_port">Relay device port</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="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 -->
<plurals name="resent_msgs_fmt">
<item quantity="one">One message sent</item>
@ -2571,8 +2603,7 @@
<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>
<!-- EXPERIMENTAL: Shown as toast when user chooses "Copy to
clipboard" for invitation -->
<string name="invite_copied">Invitation ready to paste</string>
<!-- EXPERIMENTAL: "label" for invite on clipboard. If it's shown
it's by some Android utility -->
@ -2582,4 +2613,19 @@
<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
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>

View file

@ -231,11 +231,22 @@
android:summary="@string/skip_confirm_turn_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"
/>
<PreferenceScreen android:title="@string/disable_nags_title"
>
<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"
android:title="@string/default_loc"
android:summary="@string/default_loc_summary"
@ -394,6 +405,11 @@
android:defaultValue="10998"
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 android:title="@string/pref_group_l10n_title"
@ -454,5 +470,9 @@
android:summary="@string/git_rev"
android:enabled="false"
/>
<org.eehouse.android.xw4.XWDevIDPreference
android:title="@string/devid_title"
android:enabled="false"
/>
</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">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 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
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>
<!-- -->
<!--<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="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>
<!-- -->
@ -1606,8 +1606,6 @@
<!-- -->
<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="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="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>
<!-- -->
<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_sms_title">XLATE ME: New game via SMS</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.

View file

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

View file

@ -54,15 +54,15 @@ public class BTInviteDelegate extends InviteDelegate {
private BTDevsAdapter m_adapter;
public static void launchForResult( Activity activity, int nMissing,
int requestCode )
RequestCode requestCode )
{
Assert.assertTrue( 0 < nMissing ); // don't call if nMissing == 0
Intent intent = new Intent( activity, BTInviteActivity.class );
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 );
m_activity = delegator.getActivity();

View file

@ -42,6 +42,7 @@ import java.util.Iterator;
import java.util.LinkedList;
import java.util.ListIterator;
import java.util.Map.Entry;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.LinkedBlockingQueue;
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;
import org.eehouse.android.xw4.jni.LastMoveInfo;
import org.eehouse.android.xw4.jni.XwJNI;
import org.eehouse.android.xw4.loc.LocUtils;
import junit.framework.Assert;
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 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,
SCAN,
@ -105,6 +111,7 @@ public class BTService extends XWService {
MESG_DECL,
MESG_GAMEGONE,
REMOVE_FOR,
INVITE_DUP_INVITE,
};
private class BTQueueElem {
@ -134,22 +141,30 @@ public class BTService extends XWService {
Assert.assertTrue( null != btAddr && 0 < btAddr.length() );
m_msg = buf; m_btAddr = btAddr;
m_gameID = gameID;
checkAddr();
}
public BTQueueElem( BTCmd cmd, String btAddr, int gameID ) {
this( cmd );
Assert.assertTrue( null != btAddr && 0 < btAddr.length() );
m_btAddr = btAddr;
m_gameID = gameID;
checkAddr();
}
public BTQueueElem( BTCmd cmd, NetLaunchInfo nli, String btAddr ) {
this( cmd );
m_nli = nli;
m_btAddr = btAddr;
checkAddr();
}
public int incrFailCount() { return ++m_failCount; }
public boolean failCountExceeded() { return m_failCount >= MAX_SEND_FAIL; }
private void checkAddr()
{
Assert.assertFalse( BOGUS_MARSHMALLOW_ADDR.equals( m_btAddr ) );
}
}
private BluetoothAdapter m_adapter;
@ -276,6 +291,7 @@ public class BTService extends XWService {
public static void inviteRemote( Context context, String btAddr,
NetLaunchInfo nli )
{
Assert.assertTrue( null != btAddr && 0 < btAddr.length() );
Intent intent = getIntentTo( context, BTAction.INVITE );
String nliData = nli.toString();
intent.putExtra( GAMEDATA_KEY, nliData );
@ -298,18 +314,23 @@ public class BTService extends XWService {
}
public static int enqueueFor( Context context, byte[] buf,
String targetAddr, int gameID )
CommsAddrRec targetAddr, int gameID )
{
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.putExtra( MSG_KEY, buf );
intent.putExtra( ADDR_KEY, targetAddr );
intent.putExtra( ADDR_KEY, btAddr );
intent.putExtra( GAMEID_KEY, gameID );
context.startService( intent );
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;
}
@ -373,12 +394,6 @@ public class BTService extends XWService {
String jsonData = intent.getStringExtra( GAMEDATA_KEY );
NetLaunchInfo nli = new NetLaunchInfo( this, jsonData );
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 );
m_sender.add( new BTQueueElem( BTCmd.INVITE, nli, btAddr ) );
break;
@ -457,42 +472,39 @@ public class BTService extends XWService {
}
while ( null != m_serverSocket && m_adapter.isEnabled() ) {
BluetoothSocket socket = null;
DataInputStream inStream = null;
int nRead = 0;
try {
socket = m_serverSocket.accept(); // blocks
BluetoothSocket socket = m_serverSocket.accept(); // blocks
addAddr( socket );
inStream = new DataInputStream( socket.getInputStream() );
DataInputStream inStream =
new DataInputStream( socket.getInputStream() );
byte proto = inStream.readByte();
if ( proto != BT_PROTO ) {
DataOutputStream os = new DataOutputStream( socket.getOutputStream() );
os.writeByte( BTCmd.BAD_PROTO.ordinal() );
os.flush();
socket.close();
sendBadProto( socket );
} else {
byte msg = inStream.readByte();
BTCmd cmd = BTCmd.values()[msg];
BTCmd cmd = BTCmd.values()[inStream.readByte()];
if ( protoOK( proto, cmd ) ) {
switch( cmd ) {
case PING:
receivePing( socket );
break;
case INVITE:
receiveInvitation( inStream, socket );
receiveInvitation( proto, inStream, socket );
break;
case MESG_SEND:
receiveMessage( inStream, socket );
break;
default:
DbgUtils.logf( "unexpected msg %d", msg );
DbgUtils.logf( "unexpected msg %s", cmd.toString());
break;
}
updateStatusIn( true );
} else {
DataOutputStream os =
new DataOutputStream( socket.getOutputStream() );
os.writeByte( BTCmd.BAD_PROTO.ordinal() );
os.flush();
socket.close();
sendBadProto( socket );
}
} catch ( IOException ioe ) {
DbgUtils.logf( "trying again..." );
@ -524,6 +536,12 @@ public class BTService extends XWService {
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
{
DataInputStream inStream = new DataInputStream( socket.getInputStream() );
@ -539,13 +557,21 @@ public class BTService extends XWService {
updateStatusOut( true );
}
private void receiveInvitation( DataInputStream is,
private void receiveInvitation( byte proto, DataInputStream is,
BluetoothSocket socket )
throws IOException
{
BTCmd result;
String asJson = is.readUTF();
NetLaunchInfo nli = new NetLaunchInfo( BTService.this, asJson );
NetLaunchInfo nli;
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();
addAddr( host );
@ -644,6 +670,32 @@ public class BTService extends XWService {
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 )
{
if ( null != btAddrs ) {
@ -802,7 +854,13 @@ public class BTService extends XWService {
BTCmd reply = null;
DataOutputStream outStream = connect( socket, BTCmd.INVITE );
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 );
outStream.flush();
@ -1060,16 +1118,20 @@ public class BTService extends XWService {
String btAddr )
{
BTCmd result;
if ( DictLangCache.haveDict( this, nli.lang, nli.dict ) ) {
result = makeGame( nli, btName, btAddr );
if ( checkNotDupe( nli ) ) {
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 {
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; // ???
result = BTCmd.INVITE_DUP_INVITE; // dupe of rematch
}
return result;
}
@ -1081,7 +1143,8 @@ public class BTService extends XWService {
long[] rowids = DBUtils.getRowIDsFor( BTService.this, nli.gameID() );
if ( null == rowids || 0 == rowids.length ) {
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 ) {
result = BTCmd.INVITE_FAILED;
} else {
@ -1092,7 +1155,10 @@ public class BTService extends XWService {
String body = LocUtils.getString( BTService.this,
R.string.new_bt_body_fmt,
sender );
postNotification( nli.gameID(), R.string.new_bt_title, body, rowid );
GameUtils.postInvitedNotification( this, nli.gameID(), body,
rowid );
sendResult( MultiEvent.BT_GAME_CREATED, rowid );
}
} else {
@ -1137,14 +1203,6 @@ public class BTService extends XWService {
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 )
{
ConnStatusHandler
@ -1191,9 +1249,11 @@ public class BTService extends XWService {
@Override
public int sendViaBluetooth( byte[] buf, int gameID, CommsAddrRec addr )
{
String btAddr = getSafeAddr( addr );
Assert.assertTrue( addr.contains( CommsConnType.COMMS_CONN_BT ) );
m_sender.add( new BTQueueElem( BTCmd.MESG_SEND, buf,
addr.bt_btAddr, gameID ) );
m_sender.add( new BTQueueElem( BTCmd.MESG_SEND, buf,
btAddr, gameID ) );
return buf.length;
}
}

View file

@ -23,8 +23,9 @@ package org.eehouse.android.xw4;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.DialogInterface.OnClickListener;
import android.content.Context;
import android.content.DialogInterface.OnCancelListener;
import android.content.DialogInterface.OnClickListener;
import android.content.DialogInterface.OnDismissListener;
import android.content.DialogInterface;
import android.content.Intent;
@ -68,10 +69,6 @@ public class BoardDelegate extends DelegateBase
public static final String INTENT_KEY_CHAT = "chat";
private static final int CHAT_REQUEST = 1;
private static final int BT_INVITE_RESULT = 2;
private static final int SMS_INVITE_RESULT = 3;
private static final int SCREEN_ON_TIME = 10 * 60 * 1000; // 10 mins
private static final String DLG_TITLE = "DLG_TITLE";
@ -236,11 +233,12 @@ public class BoardDelegate extends DelegateBase
}
};
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() {
public void onClick( DialogInterface dlg,
int whichButton ) {
doRematch();
doRematchIf();
}
};
ab.setNegativeButton( R.string.button_rematch, lstnr );
@ -450,7 +448,7 @@ public class BoardDelegate extends DelegateBase
dialog = ab.setTitle( "foo" )
.setMessage( "" )
.setPositiveButton( "", lstnr )
.setNegativeButton( R.string.button_close_game, lstnr2 )
.setNegativeButton( R.string.button_wait, lstnr2 )
.setOnCancelListener( new OnCancelListener() {
public void onCancel( DialogInterface dialog ) {
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 ) {
InviteMeans missingMeans = null;
switch ( requestCode ) {
case CHAT_REQUEST:
if ( BuildConstants.CHAT_SUPPORTED ) {
@ -655,14 +655,22 @@ public class BoardDelegate extends DelegateBase
}
break;
case BT_INVITE_RESULT:
missingMeans = InviteMeans.BLUETOOTH;
break;
case SMS_INVITE_RESULT:
// onActivityResult is called immediately *before*
// onResume -- meaning m_gi etc are still null.
missingMeans = InviteMeans.SMS;
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_missingCounts = data.getIntArrayExtra( InviteDelegate.COUNTS );
m_missingMeans = (BT_INVITE_RESULT == requestCode)
? InviteMeans.BLUETOOTH : InviteMeans.SMS;
break;
m_missingMeans = missingMeans;
}
}
}
@ -729,6 +737,7 @@ public class BoardDelegate extends DelegateBase
boolean inTrade = false;
MenuItem item;
int strId;
boolean enable;
if ( null != m_gsi ) {
inTrade = m_gsi.inTrade;
@ -773,10 +782,10 @@ public class BoardDelegate extends DelegateBase
Utils.setItemVisible( menu, R.id.board_menu_game_resign, !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.setVisible( enabled );
if ( enabled ) {
item.setVisible( enable );
if ( enable ) {
if ( 0 >= m_view.curPending() ) {
strId = R.string.board_menu_pass;
} 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;
Utils.setItemVisible( menu, R.id.gamel_menu_checkmoves, enable );
Utils.setItemVisible( menu, R.id.board_menu_game_resend,
@ -828,6 +840,10 @@ public class BoardDelegate extends DelegateBase
}
break;
case R.id.board_menu_rematch:
doRematchIf();
break;
case R.id.board_menu_trade_commit:
cmd = JNICmd.CMD_COMMIT;
break;
@ -1039,11 +1055,15 @@ public class BoardDelegate extends DelegateBase
break;
case BLUETOOTH:
BTInviteDelegate.launchForResult( m_activity, m_nMissing,
BT_INVITE_RESULT );
RequestCode.BT_INVITE_RESULT );
break;
case SMS:
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;
case EMAIL:
case CLIPBOARD:
@ -1661,8 +1681,8 @@ public class BoardDelegate extends DelegateBase
m_nMissing = 0;
post( new Runnable() {
public void run() {
showNotAgainDlgThen( R.string.not_again_turnchanged,
R.string.key_notagain_turnchanged );
showNotAgainDlg( R.string.not_again_turnchanged,
R.string.key_notagain_turnchanged );
}
} );
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_connTypes );
if ( null != m_xport ) {
// informMissing should have been called by now
Assert.assertNotNull( m_connTypes );
m_xport.setReceiver( m_jniThread, m_handler );
}
m_jniThread.handle( JNICmd.CMD_START );
@ -2284,7 +2306,7 @@ public class BoardDelegate extends DelegateBase
if ( BuildConstants.CHAT_SUPPORTED ) {
Intent intent = new Intent( m_activity, ChatActivity.class );
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()
{
if ( XWApp.BTSUPPORTED || XWApp.SMSSUPPORTED ) {
// test if summary knows of rematch pending first
if ( 0 < m_nMissing && m_summary.hasRematchInfo() ) {
tryRematchInvites();
} else if ( null != m_missingDevs ) {
Assert.assertNotNull( m_missingMeans );
String gameName = GameUtils.getName( m_activity, m_rowid );
m_invitesPending = m_missingDevs.length;
for ( int ii = 0; ii < m_missingDevs.length; ++ii ) {
String dev = m_missingDevs[ii];
int nPlayers = m_missingCounts[ii];
Assert.assertTrue( 0 <= m_nGuestDevs );
int forceChannel = ii + m_nGuestDevs + 1;
NetLaunchInfo nli = new NetLaunchInfo( m_summary, m_gi,
nPlayers, forceChannel );
if ( !m_relayConnected ) {
nli.removeAddress( CommsConnType.COMMS_CONN_RELAY );
}
switch ( m_missingMeans ) {
case BLUETOOTH:
if ( ! m_progressShown ) {
m_progressShown = true;
String progMsg = BTService.nameForAddr( dev );
progMsg = getString( R.string.invite_progress_fmt, progMsg );
startProgress( R.string.invite_progress_title, progMsg,
new OnCancelListener() {
public void onCancel( DialogInterface dlg )
{
m_progressShown = false;
}
});
}
BTService.inviteRemote( m_activity, dev, nli );
break;
case SMS:
SMSService.inviteRemote( m_activity, dev, nli );
break;
}
if ( 0 < m_nMissing && m_summary.hasRematchInfo() ) {
tryRematchInvites();
} else if ( null != m_missingDevs ) {
Assert.assertNotNull( m_missingMeans );
String gameName = GameUtils.getName( m_activity, m_rowid );
m_invitesPending = m_missingDevs.length;
for ( int ii = 0; ii < m_missingDevs.length; ++ii ) {
String dev = m_missingDevs[ii];
int nPlayers = m_missingCounts[ii];
Assert.assertTrue( 0 <= m_nGuestDevs );
int forceChannel = ii + m_nGuestDevs + 1;
NetLaunchInfo nli = new NetLaunchInfo( m_summary, m_gi,
nPlayers, forceChannel );
if ( !m_relayConnected ) {
nli.removeAddress( CommsConnType.COMMS_CONN_RELAY );
}
switch ( m_missingMeans ) {
case BLUETOOTH:
if ( ! m_progressShown ) {
m_progressShown = true;
String progMsg = BTService.nameForAddr( dev );
progMsg = getString( R.string.invite_progress_fmt, progMsg );
startProgress( R.string.invite_progress_title, progMsg,
new OnCancelListener() {
public void onCancel( DialogInterface dlg )
{
m_progressShown = false;
}
});
}
BTService.inviteRemote( m_activity, dev, nli );
break;
case SMS:
SMSService.inviteRemote( m_activity, dev, nli );
break;
case RELAY:
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
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;
if ( XWApp.REMATCH_SUPPORTED ) {
supported = m_gi.serverRole == DeviceRole.SERVER_STANDALONE;
if ( !supported && 2 == m_gi.nPlayers ) {
supported = m_connTypes.contains( CommsConnType.COMMS_CONN_BT )
|| m_connTypes.contains( CommsConnType.COMMS_CONN_SMS )
|| m_connTypes.contains( CommsConnType.COMMS_CONN_RELAY );
// standalone games are easy to rematch
supported = summary.serverRole == DeviceRole.SERVER_STANDALONE;
if ( !supported ) {
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;
}
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 ) {
boolean doIt = true;
String phone = null;
String btAddr = null;
String relayID = null;
if ( m_gi.serverRole != DeviceRole.SERVER_STANDALONE ) {
CommsAddrRec[] addrs = XwJNI.comms_getAddrs( m_jniGamePtr );
for ( CommsAddrRec addr : addrs ) {
if ( DeviceRole.SERVER_STANDALONE == gi.serverRole ) {
// nothing to do??
} 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 ) ) {
Assert.assertNull( btAddr );
btAddr = addr.bt_btAddr;
@ -2530,19 +2606,42 @@ public class BoardDelegate extends DelegateBase
}
if ( addr.contains( CommsConnType.COMMS_CONN_RELAY ) ) {
Assert.assertNull( relayID );
relayID = m_summary.relayID;
relayID = XwJNI.comms_formatRelayID( jniGamePtr, ii );
}
}
}
Intent intent = GamesListDelegate
.makeRematchIntent( m_activity, m_rowid, m_connTypes, btAddr,
phone, relayID );
if ( null != intent ) {
startActivity( intent );
finish();
if ( doIt ) {
CommsConnTypeSet connTypes = summary.conTypes;
String newName = summary.getRematchName();
Intent intent = GamesListDelegate
.makeRematchIntent( activity, rowid, gi.dictName,
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()
@ -2567,10 +2666,9 @@ public class BoardDelegate extends DelegateBase
if ( null != value ) {
BTService.inviteRemote( m_activity, value, nli );
}
value = m_summary.getStringExtra( GameSummary.EXTRA_REMATCH_RELAY );
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 int m_jniGamePtr;
private CommsAddrRec m_relayAddr;
private String m_useHost;
private JNIThread m_jniThread;
private CommsThread m_thread;
private TransportProcs.TPMsgHandler m_tpHandler;
@ -86,7 +87,7 @@ public class CommsTransport implements TransportProcs,
m_done = false;
boolean failed = true;
try {
if ( Build.PRODUCT.contains("sdk") ) {
if ( XWApp.onEmulator() ) {
System.setProperty("java.net.preferIPv6Addresses", "false");
}
@ -99,8 +100,7 @@ public class CommsTransport implements TransportProcs,
DbgUtils.loge( ioe );
} catch ( UnresolvedAddressException uae ) {
DbgUtils.logf( "bad address: name: %s; port: %s; exception: %s",
m_relayAddr.ip_relay_hostName,
m_relayAddr.ip_relay_port,
m_useHost, m_relayAddr.ip_relay_port,
uae.toString() );
}
@ -127,11 +127,11 @@ public class CommsTransport implements TransportProcs,
try {
m_socketChannel = SocketChannel.open();
m_socketChannel.configureBlocking( false );
DbgUtils.logf( "connecting to %s:%d",
m_relayAddr.ip_relay_hostName,
DbgUtils.logf( "connecting to %s:%d",
m_useHost,
m_relayAddr.ip_relay_port );
InetSocketAddress isa = new
InetSocketAddress(m_relayAddr.ip_relay_hostName,
InetSocketAddress(m_useHost,
m_relayAddr.ip_relay_port );
m_socketChannel.connect( isa );
} catch ( java.io.IOException ioe ) {
@ -356,9 +356,11 @@ public class CommsTransport implements TransportProcs,
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 )
{
DbgUtils.logdf( "CommsTransport.transportSend(len=%d, typ=%s)",
buf.length, conType.toString() );
int nSent = -1;
Assert.assertNotNull( addr );
Assert.assertTrue( addr.contains( conType ) );
@ -366,6 +368,7 @@ public class CommsTransport implements TransportProcs,
if ( !XWApp.UDP_ENABLED && conType == CommsConnType.COMMS_CONN_RELAY
&& null == m_relayAddr ) {
m_relayAddr = new CommsAddrRec( addr );
m_useHost = NetUtils.forceHost( m_relayAddr.ip_relay_hostName );
}
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
// fired on reconnect doesn't unstall a game but a manual
// resend does.
DbgUtils.logf( "transportSend(%d)=>%d", buf.length, nSent );
DbgUtils.logdf( "transportSend(%d)=>%d", buf.length, nSent );
return nSent;
}
@ -426,7 +429,7 @@ public class CommsTransport implements TransportProcs,
m_tpHandler.tpmRelayErrorProc( relayErr );
}
public boolean relayNoConnProc( byte[] buf, String relayID )
public boolean relayNoConnProc( byte[] buf, String msgNo, String relayID )
{
Assert.fail();
return false;
@ -447,7 +450,7 @@ public class CommsTransport implements TransportProcs,
gameID, buf );
break;
case COMMS_CONN_BT:
nSent = BTService.enqueueFor( context, buf, addr.bt_btAddr, gameID );
nSent = BTService.enqueueFor( context, buf, addr, gameID );
break;
default:
Assert.fail();

View file

@ -190,8 +190,14 @@ public class ConnStatusHandler {
R.string.connstat_net_fmt,
connTypes.toString( context )));
for ( CommsConnType typ : connTypes.getTypes() ) {
sb.append( String.format( "\n\n*** %s ***\n",
typ.longName( context ) ) );
String did = "";
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 );
tmp = LocUtils.getString( context, record.successNewer?
R.string.connstat_succ :

View file

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

View file

@ -1025,10 +1025,18 @@ public class DBUtils {
public long m_rowid;
public long m_nextNag;
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_nextNag = nextNag;
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;
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
String selection =
String.format( "%s > 0 AND %s < %s", DBHelper.NEXTNAG,
@ -1052,11 +1061,14 @@ public class DBUtils {
int rowIndex = cursor.getColumnIndex(ROW_ID);
int nagIndex = cursor.getColumnIndex( DBHelper.NEXTNAG );
int lastMoveIndex = cursor.getColumnIndex( DBHelper.LASTMOVE );
int roleIndex = cursor.getColumnIndex( DBHelper.SERVERROLE );
for ( int ii = 0; ii < result.length && cursor.moveToNext(); ++ii ) {
long rowid = cursor.getLong( rowIndex );
long nextNag = cursor.getLong( nagIndex );
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 )
{
String name = DBHelper.getDBName();
File sdcardDB = new File( Environment.getExternalStorageDirectory(),
name );
return sdcardDB.exists();
String varName = getVariantDBName();
boolean exists = new File( Environment.getExternalStorageDirectory(),
varName ).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 )
@ -2044,20 +2061,39 @@ public class DBUtils {
return dflt;
}
// public static void setIntFor( Context context, String key, int value )
// {
// String asStr = String.format( "%d", value );
// setStringFor( context, key, asStr );
// }
public static void setIntFor( Context context, String key, int value )
{
DbgUtils.logdf( "DBUtils.setIntFor(key=%s, val=%d)", key, value );
String asStr = String.format( "%d", value );
setStringFor( context, key, asStr );
}
// public static int getIntFor( Context context, String key, int dflt )
// {
// String asStr = getStringFor( context, key, null );
// if ( null != asStr ) {
// dflt = Integer.parseInt( asStr );
// }
// return dflt;
// }
public static int getIntFor( Context context, String key, int dflt )
{
String asStr = getStringFor( context, key, null );
if ( null != asStr ) {
dflt = Integer.parseInt( asStr );
}
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,
final int incr )
@ -2097,8 +2133,16 @@ public class DBUtils {
{
String name = DBHelper.getDBName();
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(),
name );
getVariantDBName() );
if ( !toSDCard && !sdcardDB.exists() ) {
sdcardDB = new File( Environment.getExternalStorageDirectory(),
name );
}
try {
File srcDB = toSDCard? gamesDB : sdcardDB;
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
// touching the SNAPSHOT column.
private static void saveChatHistory( Context context, long rowid,

View file

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

View file

@ -19,6 +19,8 @@
package org.eehouse.android.xw4;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
@ -76,12 +78,15 @@ public class DelegateBase implements DlgClickNotify,
protected void onSaveInstanceState( Bundle outState ) {}
public boolean onPrepareOptionsMenu( Menu menu ) { 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 onStop() {}
protected void onDestroy() {}
protected void onWindowFocusChanged( boolean hasFocus ) {}
protected boolean onBackPressed() { return false; }
protected void onActivityResult( int requestCode, int resultCode,
protected void onActivityResult( RequestCode requestCode, int resultCode,
Intent data )
{
DbgUtils.logf( "DelegateBase.onActivityResult(): subclass responsibility!!!" );
@ -174,9 +179,10 @@ public class DelegateBase implements DlgClickNotify,
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 )
@ -431,6 +437,13 @@ public class DelegateBase implements DlgClickNotify,
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 )
{
return m_dlgDelegate.post( runnable );

View file

@ -21,9 +21,16 @@ package org.eehouse.android.xw4;
import android.app.Activity;
import android.os.Bundle;
import android.widget.ListAdapter;
import android.widget.ListView;
public interface Delegator {
Activity getActivity();
Bundle getArguments();
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;
public class DictBrowseActivity extends XWListActivity {
public class DictBrowseActivity extends XWActivity {
private DictBrowseDelegate m_dlgt;

View file

@ -21,7 +21,6 @@
package org.eehouse.android.xw4;
import android.app.Activity;
import android.app.ListActivity;
import android.content.Context;
import android.content.Intent;
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 );
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.loc.LocUtils;
public class DictsActivity extends XWListActivity {
public class DictsActivity extends XWActivity {
private static interface SafePopup {
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.AlertDialog;
import android.app.Dialog;
import android.app.ListActivity;
import android.content.Context;
import android.content.DialogInterface.OnCancelListener;
import android.content.DialogInterface.OnClickListener;
@ -47,7 +46,7 @@ import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.TextView;
import org.apache.http.client.methods.HttpPost;
import java.net.HttpURLConnection;
import org.json.JSONArray;
import org.json.JSONException;
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,
R.menu.dicts_menu );
@ -985,7 +984,7 @@ public class DictsDelegate extends ListDelegateBase
// 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 )
{
Intent intent = new Intent( activity, DictsActivity.class );
@ -998,16 +997,16 @@ public class DictsDelegate extends ListDelegateBase
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 )
{
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 );
}
@ -1135,11 +1134,11 @@ public class DictsDelegate extends ListDelegateBase
// parse less data
String name = null;
String proc = String.format( "listDicts?lc=%s", m_lc );
HttpPost post = NetUtils.makePost( m_context, proc );
if ( null != post ) {
HttpURLConnection conn = NetUtils.makeHttpConn( m_context, proc );
if ( null != conn ) {
JSONObject theOne = null;
String langName = null;
String json = NetUtils.runPost( post, new JSONObject() );
String json = NetUtils.runConn( conn, new JSONObject() );
if ( null != json ) {
try {
JSONObject obj = new JSONObject( json );
@ -1217,9 +1216,9 @@ public class DictsDelegate extends ListDelegateBase
public Boolean doInBackground( Void... unused )
{
boolean success = false;
HttpPost post = NetUtils.makePost( m_context, "listDicts" );
if ( null != post ) {
String json = NetUtils.runPost( post, new JSONObject() );
HttpURLConnection conn = NetUtils.makeHttpConn( m_context, "listDicts" );
if ( null != conn ) {
String json = NetUtils.runConn( conn, new JSONObject() );
if ( !isCancelled() ) {
if ( null != json ) {
post( new Runnable() {

View file

@ -65,6 +65,7 @@ public class DlgDelegate {
NEW_GAME_PRESSED,
SET_HIDE_NEWGAME_BUTTONS,
DWNLD_LOC_DICT,
NEW_GAME_DFLT_NAME,
// BoardDelegate
UNDO_LAST_ACTION,
@ -138,7 +139,7 @@ public class DlgDelegate {
public interface DlgClickNotify {
public static enum InviteMeans {
SMS, EMAIL, NFC, BLUETOOTH, CLIPBOARD,
SMS, EMAIL, NFC, BLUETOOTH, CLIPBOARD, RELAY,
};
void dlgButtonClicked( Action action, int button, 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 )
{
NotAgainView naView = (NotAgainView)
final NotAgainView naView = (NotAgainView)
LocUtils.inflate( m_activity, R.layout.not_again_view );
naView.setMessage( state.m_msg );
final OnClickListener lstnr_p = mkCallbackClickListener( state, naView );
@ -571,18 +572,15 @@ public class DlgDelegate {
.setView( naView )
.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 ) {
final ActionPair more = state.m_pair;
OnClickListener lstnr = new OnClickListener() {
public void onClick( DialogInterface dlg, int item ) {
checkNotAgainCheck( state, naView );
m_clickCallback.
dlgButtonClicked( more.action,
AlertDialog.BUTTON_POSITIVE,
more.params );
lstnr_p.onClick( dlg, AlertDialog.BUTTON_POSITIVE );
}
};
builder.setNegativeButton( more.buttonStr, lstnr );
@ -631,6 +629,10 @@ public class DlgDelegate {
items.add( getString( R.string.invite_choice_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();
items.add( getString( R.string.slmenu_copy_sel ) );
means.add( DlgClickNotify.InviteMeans.CLIPBOARD );
@ -762,14 +764,7 @@ public class DlgDelegate {
OnClickListener cbkOnClickLstnr;
cbkOnClickLstnr = new OnClickListener() {
public void onClick( DialogInterface dlg, int button ) {
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();
}
}
checkNotAgainCheck( state, naView );
if ( Action.SKIP_CALLBACK != state.m_action ) {
m_clickCallback.dlgButtonClicked( state.m_action,
@ -781,6 +776,18 @@ public class DlgDelegate {
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,
final DlgState state,
DlgID dlgID )

View file

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

View file

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

View file

@ -21,7 +21,6 @@
package org.eehouse.android.xw4;
import android.app.Activity;
import android.app.ListActivity;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
@ -69,7 +68,7 @@ public class DwnldDelegate extends ListDelegateBase {
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 );
m_activity = delegator.getActivity();

View file

@ -30,6 +30,7 @@ import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.view.View;
import java.lang.ref.WeakReference;
import junit.framework.Assert;
public class ExpiringDelegate {
@ -210,7 +211,7 @@ public class ExpiringDelegate {
long nextStartIn = lastStart + onePct - now;
// 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()
{
if ( null == m_runnable ) {
m_runnable = new Runnable() {
public void run() {
m_runnable = mkRunnable( this );
}
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 ) {
DbgUtils.logf( "ExpiringDelegate: timer fired"
+ " for %H", this );
}
if ( m_active ) {
figurePct();
if ( m_haveTurnLocal ) {
m_back = null;
setBackground();
if ( dlgt.m_active ) {
dlgt.figurePct();
if ( dlgt.m_haveTurnLocal ) {
dlgt.m_back = null;
dlgt.setBackground();
}
if ( XWApp.DEBUG_EXP_TIMERS ) {
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.webkit.WebView;
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;

View file

@ -44,7 +44,7 @@ public class GCMIntentService extends GCMBaseIntentService {
protected void onRegistered( Context context, String regId )
{
DbgUtils.logf( "GCMIntentService.onRegistered(%s)", regId );
XWPrefs.setGCMDevID( context, regId );
DevID.setGCMDevID( context, regId );
notifyRelayService( context, true );
}
@ -52,7 +52,7 @@ public class GCMIntentService extends GCMBaseIntentService {
protected void onUnregistered( Context context, String regId )
{
DbgUtils.logf( "GCMIntentService.onUnregistered(%s)", regId );
XWPrefs.clearGCMDevID( context );
DevID.clearGCMDevID( context );
RelayService.devIDChanged();
notifyRelayService( context, false );
}
@ -122,7 +122,7 @@ public class GCMIntentService extends GCMBaseIntentService {
try {
GCMRegistrar.checkDevice( app );
// GCMRegistrar.checkManifest( app );
String regId = XWPrefs.getGCMDevID( app );
String regId = DevID.getGCMDevID( app );
if (regId.equals("")) {
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 WHICH_PLAYER = "WHICH_PLAYER";
private static final int REQUEST_LANG = 1;
private static final int REQUEST_DICT = 2;
private Activity m_activity;
private CheckBox m_joinPublicCheck;
@ -94,7 +92,6 @@ public class GameConfigDelegate extends DelegateBase
private boolean m_forResult;
private CurGameInfo m_gi;
private CurGameInfo m_giOrig;
private GameLock m_gameLock;
private int m_whichPlayer;
// private Spinner m_roleSpinner;
// private Spinner m_connectSpinner;
@ -509,10 +506,6 @@ public class GameConfigDelegate extends DelegateBase
protected void onPause()
{
if ( null != m_gameLock ) {
m_gameLock.unlock();
m_gameLock = null;
}
m_giOrig = null; // flag for onStart and onResume
super.onPause();
}
@ -523,7 +516,7 @@ public class GameConfigDelegate extends DelegateBase
}
@Override
protected void onActivityResult( int requestCode, int resultCode, Intent data )
protected void onActivityResult( RequestCode requestCode, int resultCode, Intent data )
{
if ( Activity.RESULT_CANCELED != resultCode ) {
loadGame();
@ -533,7 +526,7 @@ public class GameConfigDelegate extends DelegateBase
configDictSpinner( m_dictSpinner, m_gi.dictLang, dictName );
configDictSpinner( m_playerDictSpinner, m_gi.dictLang, dictName );
break;
case REQUEST_LANG:
case REQUEST_LANG_GC:
String langName = data.getStringExtra( DictsDelegate.RESULT_LAST_LANG );
selLangChanged( langName );
setLangSpinnerSelection( langName );
@ -549,10 +542,8 @@ public class GameConfigDelegate extends DelegateBase
if ( null == m_giOrig ) {
m_giOrig = new CurGameInfo( m_activity );
// Lock in case we're going to config. We *could* re-get the
// lock once the user decides to make changes. PENDING.
m_gameLock = new GameLock( m_rowid, true ).lock();
int gamePtr = GameUtils.loadMakeGame( m_activity, m_giOrig, m_gameLock );
GameLock gameLock = new GameLock( m_rowid, false ).lock();
int gamePtr = GameUtils.loadMakeGame( m_activity, m_giOrig, gameLock );
if ( 0 == gamePtr ) {
showDictGoneFinish();
} else {
@ -564,7 +555,6 @@ public class GameConfigDelegate extends DelegateBase
m_gameLockedCheck =
(CheckBox)findViewById( R.id.game_locked_check );
m_gameLockedCheck.setVisibility( View.VISIBLE );
m_gameLockedCheck.setChecked( true );
m_gameLockedCheck.setOnClickListener( this );
}
handleLockedChange();
@ -581,11 +571,14 @@ public class GameConfigDelegate extends DelegateBase
} else if ( !localOnlyGame() ) {
String relayName = XWPrefs.getDefaultRelayHost( 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();
XwJNI.game_dispose( gamePtr );
gameLock.unlock();
m_car = new CommsAddrRec( m_carOrig );
setTitle();
@ -698,7 +691,7 @@ public class GameConfigDelegate extends DelegateBase
public void onClick( View view )
{
if ( null == m_gameLock ) {
if ( isFinishing() ) {
// do nothing; we're on the way out
} else if ( m_addPlayerButton == view ) {
int curIndex = m_gi.nPlayers;
@ -756,7 +749,7 @@ public class GameConfigDelegate extends DelegateBase
protected boolean onBackPressed()
{
boolean consumed = false;
if ( null != m_gameLock ) {
if ( ! isFinishing() ) {
if ( m_forResult ) {
deleteGame();
} else {
@ -778,63 +771,64 @@ public class GameConfigDelegate extends DelegateBase
private void deleteGame()
{
if ( null != m_gameLock ) {
DBUtils.deleteGame( m_activity, m_gameLock );
m_gameLock.unlock();
m_gameLock = null;
}
GameUtils.deleteGame( m_activity, m_rowid, false );
}
private void loadPlayersList()
{
m_playerLayout.removeAllViews();
if ( !isFinishing() ) {
m_playerLayout.removeAllViews();
String[] names = m_gi.visibleNames( false );
// only enable delete if one will remain (or two if networked)
boolean canDelete = names.length > 2
|| (localOnlyGame() && names.length > 1);
View.OnClickListener lstnr = new View.OnClickListener() {
@Override
public void onClick( View view ) {
m_whichPlayer = ((XWListItem)view).getPosition();
showDialog( DlgID.PLAYER_EDIT );
}
};
String[] names = m_gi.visibleNames( false );
// only enable delete if one will remain (or two if networked)
boolean canDelete = names.length > 2
|| (localOnlyGame() && names.length > 1);
View.OnClickListener lstnr = new View.OnClickListener() {
@Override
public void onClick( View view ) {
m_whichPlayer = ((XWListItem)view).getPosition();
showDialog( DlgID.PLAYER_EDIT );
}
};
boolean localGame = localOnlyGame();
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();
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 );
}
view.setEnabled( unlocked );
view.setOnClickListener( lstnr );
m_playerLayout.addView( view );
View divider = inflate( R.layout.divider_view );
m_playerLayout.addView( divider );
}
view.setOnClickListener( lstnr );
m_playerLayout.addView( view );
m_addPlayerButton
.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 );
m_playerLayout.addView( divider );
showHideRelayStuff();
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
private void configDictSpinner( Spinner dictsSpinner, int lang,
@ -856,7 +850,7 @@ public class GameConfigDelegate extends DelegateBase
if ( chosen.equals( m_browseText ) ) {
DictsDelegate.downloadForResult( m_activity,
REQUEST_DICT,
RequestCode.REQUEST_DICT,
m_gi.dictLang );
}
}
@ -885,13 +879,17 @@ public class GameConfigDelegate extends DelegateBase
public void onItemSelected(AdapterView<?> parentView,
View selectedItemView,
int position, long id ) {
String chosen =
(String)parentView.getItemAtPosition( position );
if ( chosen.equals( m_browseText ) ) {
DictsDelegate.downloadForResult( m_activity, REQUEST_LANG );
} else {
String langName = adapter.getLangAtPosition( position );
selLangChanged( langName );
if ( ! isFinishing() ) { // not on the way out?
String chosen =
(String)parentView.getItemAtPosition( position );
if ( chosen.equals( m_browseText ) ) {
DictsDelegate.downloadForResult( m_activity,
RequestCode
.REQUEST_LANG_GC );
} else {
String langName = adapter.getLangAtPosition( position );
selLangChanged( langName );
}
}
}
@ -1044,7 +1042,15 @@ public class GameConfigDelegate extends DelegateBase
m_isLocked = locking;
for ( int id : s_disabledWhenLocked ) {
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 )
{
if ( null != m_gameLock ) {
GameUtils.applyChanges( m_activity, m_gi, m_car, m_gameLock,
if ( !isFinishing() ) {
GameLock gameLock = new GameLock( m_rowid, true ).lock();
GameUtils.applyChanges( m_activity, m_gi, m_car, gameLock,
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() ) {
showOKOnlyDialog( R.string.no_empty_rooms );
} else {
m_gameLock.unlock();
m_gameLock = null;
GameUtils.launchGameAndFinish( m_activity, m_rowid );
}
}
@ -1189,14 +1196,14 @@ public class GameConfigDelegate extends DelegateBase
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 )
{
Intent intent = new Intent( parent, GameConfigActivity.class );
intent.setAction( Intent.ACTION_EDIT );
intent.putExtra( GameUtils.INTENT_KEY_ROWID, rowID );
intent.putExtra( INTENT_FORRESULT_ROWID, true );
parent.startActivityForResult( intent, requestCode );
parent.startActivityForResult( intent, requestCode.ordinal() );
}
private void setConnLabel()

View file

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

View file

@ -124,7 +124,7 @@ public class GameListItem extends LinearLayout
++m_loadingCount;
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
task.executeOnExecutor( AsyncTask.THREAD_POOL_EXECUTOR );
} else {

View file

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

View file

@ -260,7 +260,7 @@ public class GameUtils {
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 );
return LocUtils.getString( context, R.string.game_fmt, count );
@ -455,18 +455,20 @@ public class GameUtils {
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,
MultiMsgSink sink )
MultiMsgSink sink, UtilCtxt util )
{
DbgUtils.logf( "makeNewMultiGame(nli=%s)", nli.toString() );
CommsAddrRec addr = nli.makeAddrRec( context );
return makeNewMultiGame( context, sink, DBUtils.GROUPID_UNSPEC, addr,
new int[] {nli.lang}, new String[] { nli.dict },
nli.nPlayersT, nli.nPlayersH, nli.forceChannel,
return makeNewMultiGame( context, sink, util, DBUtils.GROUPID_UNSPEC,
addr, new int[] {nli.lang},
new String[] { nli.dict }, nli.nPlayersT,
nli.nPlayersH, nli.forceChannel,
nli.inviteID(), nli.gameID(), nli.gameName,
false );
}
@ -474,35 +476,41 @@ public class GameUtils {
public static long makeNewMultiGame( Context context, long groupID,
String gameName )
{
return makeNewMultiGame( context, groupID, (CommsConnTypeSet)null,
gameName );
return makeNewMultiGame( context, groupID, null, 0,
(CommsConnTypeSet)null, gameName );
}
public static long makeNewMultiGame( Context context, long groupID,
CommsConnTypeSet addrSet, String gameName )
String dict, int lang,
CommsConnTypeSet addrSet,
String gameName )
{
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,
String inviteID, CommsConnTypeSet addrSet,
String inviteID, String dict,
int lang, CommsConnTypeSet addrSet,
String gameName )
{
int[] lang = {0};
String[] dict = {null};
int[] langArray = {lang};
String[] dictArray = {dict};
if ( null == addrSet ) {
addrSet = XWPrefs.getAddrTypes( context );
}
CommsAddrRec addr = new CommsAddrRec( addrSet );
addr.populate( context );
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 );
}
private static long makeNewMultiGame( Context context, MultiMsgSink sink,
long groupID, CommsAddrRec addr,
UtilCtxt util, long groupID,
CommsAddrRec addr,
int[] lang, String[] dict,
int nPlayersT, int nPlayersH,
int forceChannel, String inviteID,
@ -535,7 +543,7 @@ public class GameUtils {
if ( DBUtils.ROWID_NOTFOUND != rowid ) {
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();
}
@ -587,9 +595,10 @@ public class GameUtils {
addr = new CommsAddrRec( null, null );
}
String inviteID = GameUtils.formatGameID( gameID );
return makeNewMultiGame( context, sink, groupID, addr, langa, dicta,
nPlayersT, nPlayersH, forceChannel,
inviteID, gameID, gameName, isHost );
return makeNewMultiGame( context, sink, (UtilCtxt)null, groupID, addr,
langa, dicta, nPlayersT, nPlayersH,
forceChannel, inviteID, gameID, gameName,
isHost );
}
// @SuppressLint({ "NewApi", "NewApi", "NewApi", "NewApi" })
@ -987,13 +996,14 @@ public class GameUtils {
CommsAddrRec car, String inviteID,
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,
CurGameInfo gi, CommsAddrRec car,
String inviteID, GameLock lock,
boolean forceNew )
CurGameInfo gi, UtilCtxt util,
CommsAddrRec car, String inviteID,
GameLock lock, boolean forceNew )
{
// This should be a separate function, commitChanges() or
// somesuch. But: do we have a way to save changes to a gi
@ -1020,7 +1030,9 @@ public class GameUtils {
}
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,
pairs.m_paths, langName );
}
@ -1078,8 +1090,8 @@ public class GameUtils {
{
if ( null != bmr ) {
Intent intent = GamesListDelegate.makeRowidIntent( context, rowid );
String msg;
int titleID;
String msg = null;
int titleID = 0;
if ( null != bmr.m_chat ) {
titleID = R.string.notify_chat_title_fmt;
if ( null != bmr.m_chatFrom ) {
@ -1089,18 +1101,29 @@ public class GameUtils {
} else {
msg = bmr.m_chat;
}
} else {
} else if ( null != bmr.m_lmi ) {
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 {
DbgUtils.logdf( "postMoveNotification(): posting nothing for lack"
+ " 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,
boolean informNow )

View file

@ -29,14 +29,7 @@ import org.eehouse.android.xw4.jni.CurGameInfo;
import junit.framework.Assert;
public class GamesListActivity extends XWListActivity {
// 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";
public class GamesListActivity extends XWActivity {
private GamesListDelegate m_dlgt;
@Override

View file

@ -24,12 +24,14 @@ import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.content.DialogInterface;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.ContextMenu;
import android.view.Menu;
import android.view.MenuItem;
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_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 ROWID_EXTRA = "rowid";
private static final String GAMEID_EXTRA = "gameid";
private static final String REMATCH_ROWID_EXTRA = "rm_rowid";
private static final String REMATCH_DICT_EXTRA = "rm_dict";
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_BTADDR_EXTRA = "rm_btaddr";
private static final String REMATCH_PHONE_EXTRA = "rm_phone";
private static final String REMATCH_RELAYID_EXTRA = "rm_relayid";
private static final String ALERT_MSG = "alert_msg";
private class GameListAdapter extends XWExpListAdapter {
@ -561,7 +564,7 @@ public class GamesListDelegate extends ListDelegateBase
private Activity m_activity;
private static GamesListDelegate s_self;
private ListDelegator m_delegator;
private Delegator m_delegator;
private GameListAdapter m_adapter;
private Handler m_handler;
private String m_missingDict;
@ -585,8 +588,10 @@ public class GamesListDelegate extends ListDelegateBase
private boolean m_nextIsSolo;
private Button[] m_newGameButtons;
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 );
m_delegator = delegator;
@ -612,7 +617,9 @@ public class GamesListDelegate extends ListDelegateBase
public void onClick( DialogInterface dlg, int item ) {
// no name, so user must pick
if ( null == m_missingDictName ) {
DictsDelegate.downloadForResult( m_activity, REQUEST_LANG,
DictsDelegate.downloadForResult( m_activity,
RequestCode
.REQUEST_LANG_GL,
m_missingDictLang );
} else {
DwnldDelegate
@ -710,7 +717,7 @@ public class GamesListDelegate extends ListDelegateBase
String name = m_namer.getName();
DBUtils.setGroupName( m_activity,
m_groupid, name );
m_adapter.reloadGame( m_rowid );
reloadGame( m_rowid );
mkListAdapter();
}
};
@ -810,7 +817,7 @@ public class GamesListDelegate extends ListDelegateBase
}
CommonPrefs.setDefaultPlayerName( m_activity, name );
getDictForLangIf(); // hack!!!
makeThenLaunchOrConfigure();
}
});
break;
@ -821,12 +828,12 @@ public class GamesListDelegate extends ListDelegateBase
final EditText edit = (EditText)view.findViewById( R.id.edit );
lstnr = new OnClickListener() {
public void onClick( DialogInterface dlg, int item ) {
makeThenLaunchOrConfigure( edit, true );
makeThenLaunchOrConfigure( edit, true, false );
}
};
lstnr2 = new OnClickListener() {
public void onClick( DialogInterface dlg, int item ) {
makeThenLaunchOrConfigure( edit, false );
makeThenLaunchOrConfigure( edit, false, false );
}
};
@ -839,6 +846,23 @@ public class GamesListDelegate extends ListDelegateBase
.create();
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:
dialog = super.onCreateDialog( id );
break;
@ -860,25 +884,38 @@ public class GamesListDelegate extends ListDelegateBase
ad.getButton( AlertDialog.BUTTON_NEGATIVE )
.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
: R.string.new_game_message_nodflt );
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 );
if ( !m_nextIsSolo ) {
msg += "\n\n" + getString( R.string.new_game_message_net );
}
TextView edit = (TextView)dialog.findViewById( R.id.msg );
edit.setText( msg );
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;
}
}
@Override
protected void init( Bundle savedInstanceState )
{
m_handler = new Handler();
@ -914,9 +951,7 @@ public class GamesListDelegate extends ListDelegateBase
tryStartsFromIntent( getIntent() );
if ( !askDefaultNameIf() ) {
getDictForLangIf();
}
getDictForLangIf();
m_origTitle = getTitle();
} // init
@ -929,7 +964,7 @@ public class GamesListDelegate extends ListDelegateBase
m_launchedGames.clear();
Assert.assertNotNull( intent );
invalRelayIDs( intent.getStringArrayExtra( RELAYIDS_EXTRA ) );
m_adapter.reloadGame( intent.getLongExtra( ROWID_EXTRA, -1 ) );
reloadGame( intent.getLongExtra( ROWID_EXTRA, -1 ) );
tryStartsFromIntent( intent );
}
@ -1019,7 +1054,8 @@ public class GamesListDelegate extends ListDelegateBase
// OnItemLongClickListener interface
public boolean onItemLongClick( AdapterView<?> parent, View view,
int position, long id ) {
boolean success = view instanceof SelectableItem.LongClickHandler;
boolean success = ! XWApp.CONTEXT_MENUS_ENABLED
&& view instanceof SelectableItem.LongClickHandler;
if ( success ) {
((SelectableItem.LongClickHandler)view).longClicked();
}
@ -1044,7 +1080,7 @@ public class GamesListDelegate extends ListDelegateBase
if ( DBUtils.ROWIDS_ALL == rowid ) { // all changed
mkListAdapter();
} else {
m_adapter.reloadGame( rowid );
reloadGame( rowid );
}
break;
case GAME_CREATED:
@ -1173,11 +1209,13 @@ public class GamesListDelegate extends ListDelegateBase
long curID = (Long)params[0];
long newid = GameUtils.dupeGame( m_activity, curID );
m_selGames.add( newid );
if ( null != m_adapter ) {
m_adapter.reloadGame( newid );
}
reloadGame( newid );
break;
case SET_HIDE_NEWGAME_BUTTONS:
XWPrefs.setHideNewgameButtons(m_activity, true);
setupButtons();
// FALLTHRU
case NEW_GAME_PRESSED:
handleNewGame( m_nextIsSolo );
break;
@ -1199,10 +1237,6 @@ public class GamesListDelegate extends ListDelegateBase
case CLEAR_SELS:
clearSelections();
break;
case SET_HIDE_NEWGAME_BUTTONS:
XWPrefs.setHideNewgameButtons( m_activity, true );
setupButtons();
break;
case DWNLD_LOC_DICT:
String lang = (String)params[0];
String name = (String)params[1];
@ -1228,19 +1262,28 @@ public class GamesListDelegate extends ListDelegateBase
};
DwnldDelegate.downloadDictInBack( m_activity, lang, name, lstnr );
break;
case NEW_GAME_DFLT_NAME:
m_newGameParams = params;
askDefaultName();
break;
default:
Assert.fail();
}
} else if ( AlertDialog.BUTTON_NEGATIVE == which ) {
if ( Action.NEW_GAME_DFLT_NAME == action ) {
m_newGameParams = params;
makeThenLaunchOrConfigure();
}
}
}
@Override
protected void onActivityResult( int requestCode, int resultCode,
protected void onActivityResult( RequestCode requestCode, int resultCode,
Intent data )
{
boolean cancelled = Activity.RESULT_CANCELED == resultCode;
switch ( requestCode ) {
case REQUEST_LANG:
case REQUEST_LANG_GL:
if ( !cancelled ) {
DbgUtils.logf( "lang need met" );
if ( checkWarnNoDict( m_missingDictRowId ) ) {
@ -1318,11 +1361,19 @@ public class GamesListDelegate extends ListDelegateBase
.contains( XWPrefs.getDefaultNewGameGroup( m_activity ) );
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
// selected
enable = 1 == nGroupsSelected;
Utils.setItemVisible( menu, R.id.games_group_moveup,
enable && 0 <= selGroupPos );
enable && 0 < selGroupPos );
Utils.setItemVisible( menu, R.id.games_group_movedown, enable
&& (selGroupPos + 1) < groupCount );
@ -1365,10 +1416,10 @@ public class GamesListDelegate extends ListDelegateBase
{
Assert.assertTrue( m_menuPrepared );
String msg;
int itemID = item.getItemId();
boolean handled = true;
boolean changeContent = false;
boolean dropSels = false;
int groupPos = getSelGroupPos();
long groupID = DBUtils.GROUPID_UNSPEC;
if ( 0 <= groupPos ) {
@ -1400,10 +1451,6 @@ public class GamesListDelegate extends ListDelegateBase
showDialog( DlgID.NEW_GROUP );
break;
case R.id.games_game_config:
GameUtils.doConfig( m_activity, selRowIDs[0], GameConfigActivity.class );
break;
case R.id.games_menu_dicts:
DictsActivity.start( m_activity );
break;
@ -1455,102 +1502,11 @@ public class GamesListDelegate extends ListDelegateBase
showToast( R.string.db_store_done );
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:
handled = false;
handled = handleSelGamesItem( itemID, selRowIDs )
|| handleSelGroupsItem( itemID, getSelGroupIDs() );
}
if ( dropSels ) {
clearSelections();
}
if ( changeContent ) {
mkListAdapter();
}
@ -1558,6 +1514,72 @@ public class GamesListDelegate extends ListDelegateBase
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
//////////////////////////////////////////////////////////////////////
@ -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()
{
boolean hidden = XWPrefs.getHideNewgameButtons( m_activity );
@ -1741,7 +1911,7 @@ public class GamesListDelegate extends ListDelegateBase
long[] rowids = DBUtils.getRowIDsFor( m_activity, relayID );
if ( null != rowids ) {
for ( long rowid : rowids ) {
m_adapter.reloadGame( rowid );
reloadGame( rowid );
}
}
}
@ -1848,33 +2018,44 @@ public class GamesListDelegate extends ListDelegateBase
// used to connect.
private void startRematch( Intent intent )
{
if ( XWApp.REMATCH_SUPPORTED ) {
long rowid = intent.getLongExtra( REMATCH_ROWID_EXTRA, -1 );
if ( -1 != rowid ) {
String btAddr = intent.getStringExtra( REMATCH_BTADDR_EXTRA );
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 );
}
if ( XWApp.REMATCH_SUPPORTED
&& ( -1 != intent.getLongExtra( REMATCH_ROWID_EXTRA, -1 ) ) ) {
m_rematchIntent = intent;
showDialog( DlgID.GAMES_LIST_NAME_REMATCH );
}
}
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 )
{
String msg = intent.getStringExtra( ALERT_MSG );
@ -1894,16 +2075,11 @@ public class GamesListDelegate extends ListDelegateBase
}
}
private boolean askDefaultNameIf()
private void askDefaultName()
{
boolean showing =
null == CommonPrefs.getDefaultPlayerName( m_activity, 0, false );
if ( showing ) {
String name = CommonPrefs.getDefaultPlayerName( m_activity, 0, true );
CommonPrefs.setDefaultPlayerName( m_activity, name );
showDialog( DlgID.GET_NAME );
}
return showing;
String name = CommonPrefs.getDefaultPlayerName( m_activity, 0, true );
CommonPrefs.setDefaultPlayerName( m_activity, name );
showDialog( DlgID.GET_NAME );
}
private void getDictForLangIf()
@ -2185,7 +2361,9 @@ public class GamesListDelegate extends ListDelegateBase
m_adapter = new GameListAdapter();
setListAdapterKeepScroll( m_adapter );
// ListView listview = getListView();
ListView listView = getListView();
m_activity.registerForContextMenu( listView );
// String field = CommonPrefs.getSummaryField( m_activity );
// long[] positions = XWPrefs.getGroupPositions( m_activity );
// GameListAdapter adapter =
@ -2196,26 +2374,80 @@ public class GamesListDelegate extends ListDelegateBase
// 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();
long rowID;
long groupID = 1 == m_selGroupIDs.size()
? m_selGroupIDs.iterator().next() : DBUtils.GROUPID_UNSPEC;
if ( m_nextIsSolo ) {
rowID = GameUtils.saveNew( m_activity,
new CurGameInfo( m_activity ),
groupID, name );
} else {
rowID = GameUtils.makeNewMultiGame( m_activity, groupID, name );
}
boolean asking = false;
boolean skipAsk = XWPrefs
.getPrefsBoolean( m_activity, R.string.key_notagain_dfltname,
false );
if ( ! skipAsk ) {
String name1 = CommonPrefs.getDefaultPlayerName( m_activity, 0,
false );
String name2 = CommonPrefs.getDefaultOriginalPlayerName( m_activity, 0 );
if ( null == name1 || name1.equals( name2 ) ) {
asking = true;
if ( doConfigure ) {
// configure it
GameConfigDelegate.editForResult( m_activity, CONFIG_GAME, rowID );
} else {
// launch it
GameUtils.launchGame( m_activity, rowID );
String msg = LocUtils
.getString( m_activity, R.string.not_again_dfltname_fmt,
name2 );
Runnable onChecked = new Runnable() {
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,
String dict, int lang,
CommsConnTypeSet addrTypes,
String btAddr, String phone,
String relayID )
String relayID, String newName )
{
Intent intent = null;
if ( XWApp.REMATCH_SUPPORTED ) {
DbgUtils.logf( "makeRematchIntent(btAddr=%s; phone=%s)", btAddr, phone );
intent = makeSelfIntent( context );
intent.putExtra( REMATCH_ROWID_EXTRA, rowid );
intent.putExtra( REMATCH_ADDRS_EXTRA, addrTypes.toInt() );
if ( null != btAddr ) {
intent.putExtra( REMATCH_BTADDR_EXTRA, btAddr );
}
if ( null != phone ) {
intent.putExtra( REMATCH_PHONE_EXTRA, phone );
}
if ( null != relayID ) {
intent.putExtra( REMATCH_RELAYID_EXTRA, relayID );
intent.putExtra( REMATCH_DICT_EXTRA, dict );
intent.putExtra( REMATCH_LANG_EXTRA, lang );
intent.putExtra( REMATCH_NEWNAME_EXTRA, newName );
if ( null != addrTypes ) {
intent.putExtra( REMATCH_ADDRS_EXTRA, addrTypes.toInt() ); // here
if ( null != btAddr ) {
intent.putExtra( REMATCH_BTADDR_EXTRA, btAddr );
}
if ( null != phone ) {
intent.putExtra( REMATCH_PHONE_EXTRA, phone );
}
if ( null != relayID ) {
intent.putExtra( REMATCH_RELAYID_EXTRA, relayID );
}
}
}
return intent;

View file

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

View file

@ -22,18 +22,20 @@ package org.eehouse.android.xw4;
import android.app.Activity;
import android.app.Dialog;
import android.app.ListActivity;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.ListView;
import android.widget.TextView;
import android.view.ViewGroup;
import junit.framework.Assert;
abstract class InviteDelegate extends ListDelegateBase
implements View.OnClickListener {
implements View.OnClickListener,
ViewGroup.OnHierarchyChangeListener {
public static final String DEVS = "DEVS";
public static final String COUNTS = "COUNTS";
@ -44,8 +46,10 @@ abstract class InviteDelegate extends ListDelegateBase
protected Button m_rescanButton;
protected Button m_clearButton;
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 )
{
super( delegator, savedInstanceState, layoutID, R.menu.empty );
@ -67,9 +71,19 @@ abstract class InviteDelegate extends ListDelegateBase
TextView descView = (TextView)findViewById( desc_id );
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();
}
////////////////////////////////////////
// View.OnClickListener
////////////////////////////////////////
public void onClick( View 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 listSelected( String[][] devsP, int[][] countsP );
abstract void scan();

View file

@ -28,15 +28,15 @@ import android.widget.ListView;
public class ListDelegateBase extends DelegateBase {
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 )
{
this( delegator, savedInstanceState, layoutID, R.menu.empty );
}
protected ListDelegateBase( ListDelegator delegator, Bundle savedInstanceState,
protected ListDelegateBase( Delegator delegator, Bundle savedInstanceState,
int layoutID, int menuID )
{
super( delegator, savedInstanceState, layoutID, menuID );

View file

@ -21,8 +21,8 @@
package org.eehouse.android.xw4;
import android.content.Context;
import java.util.HashMap;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Set;
import junit.framework.Assert;
@ -32,7 +32,9 @@ import org.eehouse.android.xw4.jni.CommsAddrRec.CommsConnType;
public class MultiMsgSink implements TransportProcs {
private long m_rowid;
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 )
{
@ -60,7 +62,7 @@ public class MultiMsgSink implements TransportProcs {
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 )
@ -70,15 +72,15 @@ public class MultiMsgSink implements TransportProcs {
public int numSent()
{
return m_nSent;
return m_sentSet.size();
}
/***** TransportProcs interface *****/
public int getFlags() { return COMMS_XPORT_FLAGS_HASNOCONN; }
public int transportSend( byte[] buf, CommsAddrRec addr, CommsConnType typ,
int gameID )
public int transportSend( byte[] buf, String msgNo, CommsAddrRec addr,
CommsConnType typ, int gameID )
{
int nSent = -1;
switch ( typ ) {
@ -97,6 +99,10 @@ public class MultiMsgSink implements TransportProcs {
}
DbgUtils.logf( "MultiMsgSink.transportSend(): sent %d via %s",
nSent, typ.toString() );
if ( 0 < nSent ) {
DbgUtils.logdf( "MultiMsgSink.transportSend: adding %s", msgNo );
m_sentSet.add( msgNo );
}
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();
int nSent = RelayService.sendNoConnPacket( m_context, getRowID(),
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