diff --git a/xwords4/android/XWords4/AndroidManifest.xml b/xwords4/android/XWords4/AndroidManifest.xml
new file mode 100644
index 000000000..4f0fcedf0
--- /dev/null
+++ b/xwords4/android/XWords4/AndroidManifest.xml
@@ -0,0 +1,89 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/xwords4/android/XWords4/assets/OWL2_2to9.xwd b/xwords4/android/XWords4/assets/OWL2_2to9.xwd
new file mode 100644
index 000000000..a57cee7f2
Binary files /dev/null and b/xwords4/android/XWords4/assets/OWL2_2to9.xwd differ
diff --git a/xwords4/android/XWords4/build.xml b/xwords4/android/XWords4/build.xml
new file mode 100644
index 000000000..6a3b0185c
--- /dev/null
+++ b/xwords4/android/XWords4/build.xml
@@ -0,0 +1,67 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/xwords4/android/XWords4/default.properties b/xwords4/android/XWords4/default.properties
new file mode 100644
index 000000000..a1ef8e9ff
--- /dev/null
+++ b/xwords4/android/XWords4/default.properties
@@ -0,0 +1,13 @@
+# This file is automatically generated by Android Tools.
+# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
+#
+# This file must be checked in Version Control Systems.
+#
+# To customize properties used by the Ant build system use,
+# "build.properties", and override values to adapt the script to your
+# project structure.
+
+# Indicates whether an apk should be generated for each density.
+split.density=false
+# Project target.
+target=android-3
diff --git a/xwords4/android/XWords4/local.properties b/xwords4/android/XWords4/local.properties
new file mode 100644
index 000000000..d88de394b
--- /dev/null
+++ b/xwords4/android/XWords4/local.properties
@@ -0,0 +1,10 @@
+# This file is automatically generated by Android Tools.
+# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
+#
+# This file must *NOT* be checked in Version Control Systems,
+# as it contains information specific to your local configuration.
+
+# location of the SDK. This is only used by Ant
+# For customization when using a Version Control System, please read the
+# header note.
+sdk.dir=/home/andy/android-sdk-linux
diff --git a/xwords4/android/XWords4/res/drawable/icon48x48.png b/xwords4/android/XWords4/res/drawable/icon48x48.png
new file mode 100644
index 000000000..ac0c7984a
Binary files /dev/null and b/xwords4/android/XWords4/res/drawable/icon48x48.png differ
diff --git a/xwords4/android/XWords4/res/layout/board.xml b/xwords4/android/XWords4/res/layout/board.xml
new file mode 100644
index 000000000..116f0ba55
--- /dev/null
+++ b/xwords4/android/XWords4/res/layout/board.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
diff --git a/xwords4/android/XWords4/res/layout/game_config.xml b/xwords4/android/XWords4/res/layout/game_config.xml
new file mode 100644
index 000000000..6d83ec75f
--- /dev/null
+++ b/xwords4/android/XWords4/res/layout/game_config.xml
@@ -0,0 +1,58 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/xwords4/android/XWords4/res/layout/game_list.xml b/xwords4/android/XWords4/res/layout/game_list.xml
new file mode 100644
index 000000000..0fc152e5c
--- /dev/null
+++ b/xwords4/android/XWords4/res/layout/game_list.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/xwords4/android/XWords4/res/menu/board_menu.xml b/xwords4/android/XWords4/res/menu/board_menu.xml
new file mode 100644
index 000000000..daa932fae
--- /dev/null
+++ b/xwords4/android/XWords4/res/menu/board_menu.xml
@@ -0,0 +1,59 @@
+
+
+
diff --git a/xwords4/android/XWords4/res/menu/games_list_item_menu.xml b/xwords4/android/XWords4/res/menu/games_list_item_menu.xml
new file mode 100644
index 000000000..56ca860e6
--- /dev/null
+++ b/xwords4/android/XWords4/res/menu/games_list_item_menu.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
diff --git a/xwords4/android/XWords4/res/values/strings.xml b/xwords4/android/XWords4/res/values/strings.xml
new file mode 100644
index 000000000..6883dd644
--- /dev/null
+++ b/xwords4/android/XWords4/res/values/strings.xml
@@ -0,0 +1,74 @@
+
+
+
+
+ Delete
+ Add game
+ Add game
+ Revert
+ Discard
+
+ Open
+ View
+ Hide
+ Delete
+
+ Edit note
+ Edit title
+
+ Play game
+
+ Create game
+ Edit game
+ Crosswords
+ Game
+ Game name:
+
+ Crosswords
+
+ OK
+
+ Name:
+ Open
+ Done
+
+ Error
+ Error loading game
+
+
+ Turn done
+ Juggle
+ Flip
+ Trade
+ Hide/Show
+ Undo
+ Undo current
+ Undo last
+ Hint
+ Next hint
+ Hint
+ Game
+ Counts and values
+ Tiles remaining
+ Game info
+ Game history
+ Final scores
+ Resend messages
+ File
+ New game
+ Preferences
+ About Crosswords
+
diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/BoardActivity.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/BoardActivity.java
new file mode 100644
index 000000000..a38fd2863
--- /dev/null
+++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/BoardActivity.java
@@ -0,0 +1,205 @@
+
+
+package org.eehouse.android.xw4;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.MenuInflater;
+import android.content.res.AssetManager;
+import java.io.InputStream;
+import android.os.Handler;
+
+import org.eehouse.android.xw4.jni.*;
+
+public class BoardActivity extends Activity implements XW_UtilCtxt, Runnable {
+
+ private BoardView m_view;
+ private int m_jniGamePtr;
+ private CurGameInfo m_gi;
+ private CommonPrefs m_prefs;
+ private Handler m_handler;
+ private TimerRunnable[] m_timers;
+
+ public class TimerRunnable implements Runnable {
+ private int m_gamePtr;
+ private int m_why;
+ private int m_when;
+ private int m_handle;
+ private TimerRunnable( int why, int when, int handle ) {
+ m_why = why;
+ m_when = when;
+ m_handle = handle;
+ }
+ public void run() {
+ Utils.logf( "TimerRunnable.run(); m_handle=" + m_handle );
+ m_timers[m_why] = null;
+ XwJNI.timerFired( m_jniGamePtr, m_why, m_when, m_handle );
+ }
+ }
+
+ protected void onCreate( Bundle savedInstanceState ) {
+ super.onCreate( savedInstanceState );
+
+ setContentView( R.layout.board );
+ m_handler = new Handler();
+ m_timers = new TimerRunnable[4]; // needs to be in sync with XWTimerReason
+
+ m_prefs = new CommonPrefs();
+ m_gi = new CurGameInfo();
+
+ m_view = (BoardView)findViewById( R.id.board_view );
+
+ byte[] dictBytes = null;
+ InputStream dict = null;
+ AssetManager am = getAssets();
+ try {
+ dict = am.open( m_gi.dictName,
+ android.content.res.AssetManager.ACCESS_RANDOM );
+ Utils.logf( "opened dict" );
+
+ int len = dict.available();
+ Utils.logf( "dict size: " + len );
+ dictBytes = new byte[len];
+ int nRead = dict.read( dictBytes, 0, len );
+ if ( nRead != len ) {
+ Utils.logf( "**** warning ****; read only " + nRead + " of "
+ + len + " bytes." );
+ }
+ } catch ( java.io.IOException ee ){
+ Utils.logf( "failed to open" );
+ }
+ // am.close(); don't close! won't work subsequently
+
+ Utils.logf( "calling game_makeNewGame; passing bytes: " + dictBytes.length );
+ m_jniGamePtr = XwJNI.game_makeNewGame( m_gi, this, m_view, 0,
+ m_prefs, null, dictBytes );
+ m_view.startHandling( m_jniGamePtr, m_gi );
+
+ XwJNI.server_do( m_jniGamePtr );
+ }
+
+ public boolean onCreateOptionsMenu(Menu menu) {
+ MenuInflater inflater = getMenuInflater();
+ inflater.inflate( R.menu.board_menu, menu );
+ return true;
+ }
+
+ private boolean toggleTray() {
+ boolean draw;
+ int state = XwJNI.board_getTrayVisState( m_jniGamePtr );
+ if ( state == XwJNI.TRAY_REVEALED ) {
+ draw = XwJNI.board_hideTray( m_jniGamePtr );
+ } else {
+ draw = XwJNI.board_showTray( m_jniGamePtr );
+ }
+ return draw;
+ }
+
+
+ public boolean onOptionsItemSelected(MenuItem item) {
+ boolean draw = false;
+ boolean handled = true;
+
+ switch (item.getItemId()) {
+ case R.id.board_menu_done:
+ draw = XwJNI.board_commitTurn( m_jniGamePtr );
+ break;
+ case R.id.board_menu_juggle:
+ draw = XwJNI.board_juggleTray( m_jniGamePtr );
+ break;
+ case R.id.board_menu_flip:
+ draw = XwJNI.board_flip( m_jniGamePtr );
+ break;
+ case R.id.board_menu_tray:
+ draw = toggleTray();
+ break;
+
+ case R.id.board_menu_undo_current:
+ draw = XwJNI.board_replaceTiles( m_jniGamePtr );
+ break;
+ case R.id.board_menu_undo_last:
+ XwJNI.server_handleUndo( m_jniGamePtr );
+ draw = true;
+ break;
+
+ case R.id.board_menu_hint:
+ XwJNI.board_resetEngine( m_jniGamePtr );
+ // fallthru
+ case R.id.board_menu_hint_next:
+ draw = XwJNI.board_requestHint( m_jniGamePtr, false, null );
+ break;
+
+ default:
+ Utils.logf( "menuitem " + item.getItemId() + " not handled" );
+ handled = false;
+ }
+
+ if ( draw ) {
+ m_view.invalidate();
+ }
+
+ return handled;
+ }
+
+ //////////////////////////////////////////
+ // XW_UtilCtxt interface implementation //
+ //////////////////////////////////////////
+ static final int[] s_buttsBoard = {
+ BONUS_TRIPLE_WORD, BONUS_NONE, BONUS_NONE,BONUS_DOUBLE_LETTER,BONUS_NONE,BONUS_NONE,BONUS_NONE,BONUS_TRIPLE_WORD,
+ BONUS_NONE, BONUS_DOUBLE_WORD, BONUS_NONE,BONUS_NONE,BONUS_NONE,BONUS_TRIPLE_LETTER,BONUS_NONE,BONUS_NONE,
+
+ BONUS_NONE, BONUS_NONE, BONUS_DOUBLE_WORD,BONUS_NONE,BONUS_NONE,BONUS_NONE,BONUS_DOUBLE_LETTER,BONUS_NONE,
+ BONUS_DOUBLE_LETTER,BONUS_NONE, BONUS_NONE,BONUS_DOUBLE_WORD,BONUS_NONE,BONUS_NONE,BONUS_NONE,BONUS_DOUBLE_LETTER,
+
+ BONUS_NONE, BONUS_NONE, BONUS_NONE,BONUS_NONE,BONUS_DOUBLE_WORD,BONUS_NONE,BONUS_NONE,BONUS_NONE,
+ BONUS_NONE, BONUS_TRIPLE_LETTER,BONUS_NONE,BONUS_NONE,BONUS_NONE,BONUS_TRIPLE_LETTER,BONUS_NONE,BONUS_NONE,
+
+ BONUS_NONE, BONUS_NONE, BONUS_DOUBLE_LETTER,BONUS_NONE,BONUS_NONE,BONUS_NONE,BONUS_DOUBLE_LETTER,BONUS_NONE,
+ BONUS_TRIPLE_WORD, BONUS_NONE, BONUS_NONE,BONUS_DOUBLE_LETTER,BONUS_NONE,BONUS_NONE,BONUS_NONE,BONUS_DOUBLE_WORD,
+ }; /* buttsBoard */
+
+ public int getSquareBonus( int col, int row ) {
+ int bonus = BONUS_NONE;
+ if ( col > 7 ) { col = 14 - col; }
+ if ( row > 7 ) { row = 14 - row; }
+ int index = (row*8) + col;
+ if ( index < 8*8 ) {
+ bonus = s_buttsBoard[index];
+ }
+ return bonus;
+ }
+
+ public void run() {
+ Utils.logf( "run called" );
+ if ( XwJNI.server_do( m_jniGamePtr ) ) {
+ m_view.invalidate();
+ }
+ }
+
+ public void requestTime() {
+ m_handler.post( this );
+ }
+
+ public void setTimer( int why, int when, int handle )
+ {
+ Utils.logf( "setTimer called" );
+
+ if ( null != m_timers[why] ) {
+ m_handler.removeCallbacks( m_timers[why] );
+ }
+
+ m_timers[why] = new TimerRunnable( why, when, handle );
+ m_handler.postDelayed( m_timers[why], 500 );
+ }
+
+ public void clearTimer( int why )
+ {
+ Utils.logf( "clearTimer called" );
+ if ( null != m_timers[why] ) {
+ m_handler.removeCallbacks( m_timers[why] );
+ m_timers[why] = null;
+ }
+ }
+}
diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/BoardView.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/BoardView.java
new file mode 100644
index 000000000..5ecd94934
--- /dev/null
+++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/BoardView.java
@@ -0,0 +1,290 @@
+
+
+package org.eehouse.android.xw4;
+
+import android.view.View;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.content.Context;
+import android.util.AttributeSet;
+import org.eehouse.android.xw4.jni.*;
+import android.view.MotionEvent;
+
+public class BoardView extends View implements DrawCtx,
+ BoardHandler {
+
+ private Paint m_fillPaint;
+ private Paint m_strokePaint;
+ private int m_jniGamePtr;
+ private CurGameInfo m_gi;
+ private boolean m_boardSet = false;
+ private Canvas m_canvas;
+ private int m_trayOwner;
+ private Rect m_valRect;
+ private Rect m_letterRect;
+
+ private static final int BLACK = 0xFF000000;
+ private static final int WHITE = 0xFFFFFFFF;
+ private static final int TILE_BACK = 0xFFFFFF99;
+ private int [] m_bonusColors = { WHITE, // BONUS_NONE
+ 0xFFAFAF00, /* bonus 1 */
+ 0xFF00AFAF,
+ 0xFFAF00AF,
+ 0xFFAFAFAF };
+ private static final int[] m_playerColors = {
+ 0xFF000000,
+ 0xFFFF0000,
+ 0xFF0000FF,
+ 0xFF008F00,
+ };
+
+
+
+ public BoardView( Context context ) {
+ super( context );
+ init();
+ }
+
+ // called when inflating xml
+ public BoardView( Context context, AttributeSet attrs ) {
+ super( context, attrs );
+ init();
+ }
+
+ // public boolean onClick( View view ) {
+ // Utils.logf( "onClick called" );
+ // return view == this;
+ // }
+
+ public boolean onTouchEvent( MotionEvent event ) {
+ int action = event.getAction();
+ int xx = (int)event.getX();
+ int yy = (int)event.getY();
+ boolean draw = false;
+
+ switch ( action ) {
+ case MotionEvent.ACTION_DOWN:
+ boolean[] handled = new boolean[1];
+ draw = XwJNI.board_handlePenDown( m_jniGamePtr, xx, yy, handled );
+ break;
+ case MotionEvent.ACTION_MOVE:
+ draw = XwJNI.board_handlePenMove( m_jniGamePtr, xx, yy );
+ break;
+ case MotionEvent.ACTION_UP:
+ draw = XwJNI.board_handlePenUp( m_jniGamePtr, xx, yy );
+ break;
+ default:
+ Utils.logf( "unknown action: " + action );
+ Utils.logf( event.toString() );
+ }
+ if ( draw ) {
+ invalidate();
+ }
+ return true; // required to get subsequent events
+ }
+
+ protected void onDraw( Canvas canvas ) {
+ if ( layoutBoardOnce() ) {
+ m_canvas = canvas; // save for callbacks
+ XwJNI.board_invalAll( m_jniGamePtr ); // get rid of this!
+ if ( XwJNI.board_draw( m_jniGamePtr ) ) {
+ Utils.logf( "draw succeeded" );
+ }
+ m_canvas = null; // clear!
+ }
+ }
+
+ private void init() {
+ setFocusable(true);
+ setBackgroundDrawable( null );
+
+ m_fillPaint = new Paint();
+ m_fillPaint.setTextAlign( Paint.Align.CENTER ); // center horizontally
+ m_strokePaint = new Paint();
+ m_strokePaint.setStyle( Paint.Style.STROKE );
+
+ // Move this to finalize?
+ // XwJNI.game_dispose( jniGamePtr );
+ // Utils.logf( "game_dispose returned" );
+ // jniGamePtr = 0;
+ }
+
+ private boolean layoutBoardOnce() {
+ if ( !m_boardSet && null != m_gi ) {
+ m_boardSet = true;
+
+ // For now we're assuming vertical orientation. Fix way
+ // later.
+
+ int width = getWidth();
+ int height = getHeight();
+ int nCells = m_gi.boardSize;
+ int cellSize = width / nCells;
+ int border = (width % nCells) / 2;
+ int scoreHt = cellSize; // scoreboard ht same as cells for
+ // proportion
+
+ XwJNI.board_setPos( m_jniGamePtr, border, border+cellSize, false );
+ XwJNI.board_setScale( m_jniGamePtr, cellSize, cellSize );
+ XwJNI.board_setScoreboardLoc( m_jniGamePtr, border, border,
+ nCells * cellSize, // width
+ scoreHt, true );
+
+ XwJNI.board_setTrayLoc( m_jniGamePtr, border,
+ border + scoreHt + (nCells * cellSize),
+ nCells * cellSize, // width
+ cellSize * 2, // height
+ 4 );
+ XwJNI.board_setShowColors( m_jniGamePtr, true );
+ XwJNI.board_invalAll( m_jniGamePtr );
+ }
+ return m_boardSet;
+ }
+
+ public void startHandling( int gamePtr, CurGameInfo gi ) {
+ m_jniGamePtr = gamePtr;
+ m_gi = gi;
+ }
+
+ // DrawCtxt interface implementation
+ public void measureRemText( Rect r, int nTilesLeft, int[] width, int[] height ) {
+ width[0] = 30;
+ height[0] = r.bottom - r.top;
+ }
+
+ public void measureScoreText( Rect r, DrawScoreInfo dsi, int[] width, int[] height )
+ {
+ width[0] = 60;
+ height[0] = r.bottom - r.top;
+ }
+
+ public void drawRemText( Rect rInner, Rect rOuter, int nTilesLeft, boolean focussed )
+ {
+ String text = String.format( "%d", nTilesLeft ); // should cache a formatter
+ m_fillPaint.setColor( TILE_BACK );
+ m_canvas.drawRect( rOuter, m_fillPaint );
+
+ m_fillPaint.setTextSize( rInner.bottom - rInner.top );
+ m_fillPaint.setColor( BLACK );
+ drawCentered( text, rInner );
+ }
+
+ public void score_drawPlayer( Rect rInner, Rect rOuter, DrawScoreInfo dsi )
+ {
+ String text = String.format( "%s:%d", dsi.name, dsi.totalScore );
+ m_fillPaint.setTextSize( rInner.bottom - rInner.top );
+ m_fillPaint.setColor( m_playerColors[dsi.playerNum] );
+ drawCentered( text, rInner );
+ }
+
+ public boolean drawCell( Rect rect, String text, Object[] bitmaps, int tile,
+ int owner, int bonus, int hintAtts, int flags )
+ {
+ int backColor;
+ int foreColor = WHITE; // must be initialized :-(
+ boolean empty = null == text && null == bitmaps;
+ boolean pending = 0 != (flags & CELL_HIGHLIGHT);
+ if ( empty ) {
+ backColor = m_bonusColors[bonus];
+ } else if ( pending ) {
+ backColor = BLACK;
+ } else {
+ backColor = TILE_BACK;
+ foreColor = m_playerColors[owner];
+ }
+
+ m_fillPaint.setColor( backColor );
+ m_canvas.drawRect( rect, m_fillPaint );
+
+ if ( !empty ) {
+ m_fillPaint.setTextSize( rect.bottom - rect.top );
+ if ( owner < 0 ) { // showColors option not turned on
+ owner = 0;
+ }
+ m_fillPaint.setColor( foreColor );
+ drawCentered( text, rect );
+ }
+
+ m_canvas.drawRect( rect, m_strokePaint );
+ return true;
+ }
+
+ public boolean trayBegin ( Rect rect, int owner, int dfs ) {
+ m_trayOwner = owner;
+ return true;
+ }
+
+ public void drawTile( Rect rect, String text, Object[] bitmaps, int val,
+ int flags ) {
+ boolean valHidden = (flags & CELL_VALHIDDEN) != 0;
+ boolean notEmpty = (flags & CELL_ISEMPTY) == 0;
+ boolean isCursor = (flags & CELL_ISCURSOR) != 0;
+
+ if ( isCursor || notEmpty ) {
+ m_fillPaint.setColor( TILE_BACK );
+ m_canvas.drawRect( rect, m_fillPaint );
+
+ if ( null != text ) {
+ m_fillPaint.setColor( m_playerColors[m_trayOwner] );
+ positionDrawTile( rect, text, val );
+ }
+
+ m_canvas.drawRect( rect, m_strokePaint ); // frame
+ }
+ }
+
+ public void drawTileMidDrag ( Rect rect, String text, Object[] bitmaps,
+ int val, int owner, int flags )
+ {
+ drawTile( rect, text, bitmaps, val, flags );
+ }
+
+ public void drawTileBack( Rect rect, int flags )
+ {
+ drawTile( rect, "?", null, 0, flags );
+ }
+
+ public void drawTrayDivider( Rect rect, int flags )
+ {
+ m_fillPaint.setColor( BLACK ); // black for now
+ m_canvas.drawRect( rect, m_fillPaint );
+ }
+
+ public void score_pendingScore( Rect rect, int score, int playerNum,
+ int flags )
+ {
+ String text = score >= 0? String.format( "%d", score ) : "??";
+ m_fillPaint.setColor( BLACK );
+ m_fillPaint.setTextSize( (rect.bottom - rect.top) / 2 );
+ drawCentered( text, rect );
+ }
+
+
+ private void drawCentered( String text, Rect rect ) {
+ int bottom = rect.bottom;
+ int center = rect.left + ( (rect.right - rect.left) / 2 );
+ m_canvas.drawText( text, center, bottom, m_fillPaint );
+ }
+
+ private void positionDrawTile( Rect rect, String text, int val )
+ {
+ if ( null == m_letterRect ) {
+ // assumes show values is on
+ m_letterRect = new Rect( 0, 0, rect.width() * 3 / 4, rect.height() * 3 / 4 );
+ }
+ m_letterRect.offsetTo( rect.left, rect.top );
+ m_fillPaint.setTextSize( m_letterRect.height() );
+ drawCentered( text, m_letterRect );
+
+ if ( null == m_valRect ) {
+ m_valRect = new Rect( 0, 0, rect.width() / 4, rect.height() / 4 );
+ }
+ m_valRect.offsetTo( rect.right - (rect.width() / 4),
+ rect.bottom - (rect.height() / 4) );
+ text = String.format( "%d", val );
+ m_fillPaint.setTextSize( m_valRect.height() );
+ drawCentered( text, m_valRect );
+ }
+
+}
\ No newline at end of file
diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameConfig.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameConfig.java
new file mode 100644
index 000000000..90db1e0d1
--- /dev/null
+++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameConfig.java
@@ -0,0 +1,183 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.eehouse.android.xw4;
+
+import android.app.Activity;
+import android.content.ComponentName;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.database.Cursor;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.net.Uri;
+import android.os.Bundle;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.widget.EditText;
+import android.view.View;
+import android.widget.Button;
+import android.widget.EditText;
+import java.io.PrintStream;
+import java.io.FileOutputStream;
+import android.text.Editable;
+
+/**
+ * A generic activity for editing a note in a database. This can be used
+ * either to simply view a note {@link Intent#ACTION_VIEW}, view and edit a note
+ * {@link Intent#ACTION_EDIT}, or create a new note {@link Intent#ACTION_INSERT}.
+ */
+public class GameConfig extends Activity implements View.OnClickListener {
+ private static final String TAG = "Games";
+
+ /** The index of the note column */
+ private static final int COLUMN_INDEX_NOTE = 1;
+
+ // This is our state data that is stored when freezing.
+ private static final String ORIGINAL_CONTENT = "origContent";
+
+ // Identifiers for our menu items.
+ private static final int REVERT_ID = Menu.FIRST;
+ private static final int DISCARD_ID = Menu.FIRST + 1;
+ private static final int DELETE_ID = Menu.FIRST + 2;
+
+ // The different distinct states the activity can be run in.
+ private static final int STATE_EDIT = 0;
+ private static final int STATE_INSERT = 1;
+
+ private int mState;
+ private boolean mNoteOnly = false;
+ private Uri mUri;
+ private String mOriginalContent;
+
+ private Button mDoneB;
+ private Button mOpenB;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ final Intent intent = getIntent();
+
+ // Do some setup based on the action being performed.
+
+ final String action = intent.getAction();
+ Utils.logf( "action: " + action );
+ if (Intent.ACTION_EDIT.equals(action)) {
+ // Requested to edit: set that state, and the data being edited.
+ mState = STATE_EDIT;
+ mUri = intent.getData();
+ } else if (Intent.ACTION_INSERT.equals(action)) {
+ Utils.logf( "matches insert" );
+ // Requested to insert: set that state, and create a new entry
+ // in the container.
+ mState = STATE_INSERT;
+ // mUri = getContentResolver().insert(intent.getData(), null);
+ // Utils.logf( mUri.toString() );
+
+ // If we were unable to create a new note, then just finish
+ // this activity. A RESULT_CANCELED will be sent back to the
+ // original activity if they requested a result.
+ // if (mUri == null) {
+ // Log.e(TAG, "Failed to insert new note into " + getIntent().getData());
+ // finish();
+ // return;
+ // }
+
+ // The new entry was created, so assume all will end well and
+ // set the result to be returned.
+ // setResult(RESULT_OK, (new Intent()).setAction(mUri.toString()));
+
+ } else {
+ // Whoops, unknown action! Bail.
+ Utils.logf("Unknown action, exiting");
+ finish();
+ return;
+ }
+
+ setContentView(R.layout.game_config);
+
+ mOpenB = (Button)findViewById(R.id.game_config_open);
+ mOpenB.setOnClickListener( this );
+ mDoneB = (Button)findViewById(R.id.game_config_done);
+ mDoneB.setOnClickListener( this );
+
+ // If an instance of this activity had previously stopped, we can
+ // get the original text it started with.
+ // if (savedInstanceState != null) {
+ // mOriginalContent = savedInstanceState.getString(ORIGINAL_CONTENT);
+ // }
+ } // onCreate
+
+ public void onClick( View view ) {
+
+ if ( mDoneB == view ) {
+ EditText et = (EditText)findViewById( R.id.player_1_name );
+ Editable text = et.getText();
+ String name1 = text.toString();
+ et = (EditText)findViewById( R.id.player_2_name );
+ text = et.getText();
+ String name2 = text.toString();
+
+ if ( name1.length() > 0 && name2.length() > 0 ) {
+ Integer num = 0;
+ int ii;
+ String[] files = fileList();
+ String name = null;
+
+ while ( name == null ) {
+ name = "game " + num.toString();
+ for ( ii = 0; ii < files.length; ++ii ) {
+ Utils.logf( "comparing " + name + " with " + files[ii] );
+ if ( files[ii].equals(name) ) {
+ ++num;
+ name = null;
+ }
+ }
+ }
+ Utils.logf( "using name " + name );
+
+ FileOutputStream out;
+ try {
+ out = openFileOutput( name, MODE_PRIVATE );
+ PrintStream ps = new PrintStream( out );
+ ps.println( name1 );
+ ps.println( name2 );
+ ps.close();
+ try {
+ out.close();
+ } catch ( java.io.IOException ex ) {
+ Utils.logf( "got IOException: " + ex.toString() );
+ }
+ } catch ( java.io.FileNotFoundException ex ) {
+ Utils.logf( "got FileNotFoundException: " + ex.toString() );
+ }
+ }
+ finish();
+ } else if ( mOpenB == view ) {
+ // finish but after posting an intent that'll cause the
+ // list view to launch us -- however that's done.
+ Utils.logf( "got open" );
+ } else {
+ Utils.logf( "unknown v: " + view.toString() );
+ }
+ } // onClick
+
+}
diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameListAdapter.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameListAdapter.java
new file mode 100644
index 000000000..bce3b6537
--- /dev/null
+++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameListAdapter.java
@@ -0,0 +1,93 @@
+
+
+package org.eehouse.android.xw4;
+
+import android.widget.Adapter;
+import android.widget.ListAdapter;
+import android.content.Context;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+import android.database.DataSetObserver;
+import java.io.FileInputStream;
+import java.io.InputStreamReader;
+import java.nio.CharBuffer;
+
+public class GameListAdapter implements ListAdapter {
+ Context m_context;
+ String[] m_files;
+
+ public GameListAdapter( Context context ) {
+ m_context = context;
+ m_files = context.fileList();
+ }
+
+ public boolean areAllItemsEnabled() {
+ return true;
+ }
+
+ public boolean isEnabled( int position ) {
+ return true;
+ }
+
+ public int getCount() {
+ return m_files.length;
+ }
+
+
+ public Object getItem( int position ) {
+ TextView view = new TextView(m_context);
+
+ try {
+ FileInputStream in = m_context.openFileInput( m_files[position] );
+
+ InputStreamReader reader = new InputStreamReader( in );
+ try {
+ int len = in.available();
+ CharBuffer buf = CharBuffer.allocate(len);
+ reader.read( buf.array(), 0, len );
+ reader.close();
+ view.setText( buf );
+ } catch ( java.io.IOException ex ) {
+ Utils.logf( "got IOException: " + ex.toString() );
+ }
+
+ try {
+ in.close();
+ } catch ( java.io.IOException ex ) {
+ Utils.logf( "got IOException: " + ex.toString() );
+ }
+ } catch ( java.io.FileNotFoundException ex ) {
+ Utils.logf( "got FileNotFoundException: " + ex.toString() );
+ }
+
+ return view;
+ }
+
+ public long getItemId( int position ) {
+ return position;
+ }
+
+ public int getItemViewType( int position ) {
+ return 0;
+ }
+
+ public View getView( int position, View convertView, ViewGroup parent ) {
+ return (View)getItem( position );
+ }
+
+ public int getViewTypeCount() {
+ return 1;
+ }
+
+ public boolean hasStableIds() {
+ return true;
+ }
+
+ public boolean isEmpty() {
+ return getCount() == 0;
+ }
+
+ public void registerDataSetObserver(DataSetObserver observer) {}
+ public void unregisterDataSetObserver(DataSetObserver observer) {}
+}
\ No newline at end of file
diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GamesList.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GamesList.java
new file mode 100644
index 000000000..25c272d3c
--- /dev/null
+++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GamesList.java
@@ -0,0 +1,253 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.eehouse.android.xw4;
+
+import android.app.ListActivity;
+import android.content.ComponentName;
+import android.content.ContentUris;
+import android.content.Intent;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.ContextMenu;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ContextMenu.ContextMenuInfo;
+import android.widget.AdapterView;
+import android.widget.ListView;
+import android.widget.SimpleCursorAdapter;
+import android.content.res.AssetManager;
+import java.io.InputStream;
+import android.widget.Button;
+import android.view.MenuInflater;
+
+import org.eehouse.android.xw4.XWords4.Games; // for constants
+
+public class GamesList extends ListActivity implements View.OnClickListener {
+ private static final String TAG = "GamesList";
+ // private InputStream m_dict;
+ private GameListAdapter m_adapter;
+
+ // Menu item ids
+ public static final int MENU_ITEM_DELETE = Menu.FIRST;
+ public static final int MENU_ITEM_INSERT = Menu.FIRST + 1;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+
+ setContentView(R.layout.game_list);
+
+ // setDefaultKeyMode(DEFAULT_KEYS_SHORTCUT);
+
+ // AssetManager am = getAssets();
+ // try {
+ // m_dict = am.open( "BasEnglish2to8.xwd",
+ // android.content.res.AssetManager.ACCESS_RANDOM );
+ // Utils.logf( "opened" );
+ // } catch ( java.io.IOException ee ){
+ // m_dict = null;
+ // Utils.logf( "failed to open" );
+ // }
+
+ m_adapter = new GameListAdapter( this );
+ setListAdapter( m_adapter );
+
+ registerForContextMenu( getListView() );
+
+ Button newGameB = (Button)findViewById(R.id.new_game);
+ newGameB.setOnClickListener( this );
+ Utils.logf( "got button" );
+
+ // If no data was given in the intent (because we were started
+ // as a MAIN activity), then use our default content provider.
+ Intent intent = getIntent();
+ Utils.logf( intent.toString() );
+ if (intent.getData() == null) {
+ intent.setData(Games.CONTENT_URI);
+ }
+
+ // Inform the list we provide context menus for items
+ // getListView().setOnCreateContextMenuListener(this);
+
+ // Perform a managed query. The Activity will handle closing
+ // and requerying the cursor when needed.
+ // Cursor cursor = managedQuery(getIntent().getData(), PROJECTION, null, null,
+ // Notes.DEFAULT_SORT_ORDER);
+
+ // Used to map notes entries from the database to views
+ // SimpleCursorAdapter adapter =
+ // new SimpleCursorAdapter(this, R.layout.noteslist_item, cursor,
+ // new String[] { Notes.TITLE },
+ // new int[] { android.R.id.text1 });
+ // setListAdapter(adapter);
+ }
+
+ // @Override
+ // public boolean onCreateOptionsMenu(Menu menu) {
+ // super.onCreateOptionsMenu(menu);
+
+ // // This is our one standard application action -- inserting a
+ // // new note into the list.
+ // menu.add(0, MENU_ITEM_INSERT, 0, R.string.menu_insert)
+ // .setShortcut('3', 'a')
+ // .setIcon(android.R.drawable.ic_menu_add);
+
+ // // Generate any additional actions that can be performed on the
+ // // overall list. In a normal install, there are no additional
+ // // actions found here, but this allows other applications to extend
+ // // our menu with their own actions.
+ // Intent intent = new Intent(null, getIntent().getData());
+ // intent.addCategory(Intent.CATEGORY_ALTERNATIVE);
+ // menu.addIntentOptions(Menu.CATEGORY_ALTERNATIVE, 0, 0,
+ // new ComponentName(this, GamesList.class), null, intent, 0, null);
+
+ // return true;
+ // }
+
+ // @Override
+ // public boolean onPrepareOptionsMenu(Menu menu) {
+ // super.onPrepareOptionsMenu(menu);
+ // final boolean haveItems = getListAdapter().getCount() > 0;
+
+ // // If there are any notes in the list (which implies that one of
+ // // them is selected), then we need to generate the actions that
+ // // can be performed on the current selection. This will be a combination
+ // // of our own specific actions along with any extensions that can be
+ // // found.
+ // if (haveItems) {
+ // // This is the selected item.
+ // Uri uri = ContentUris.withAppendedId(getIntent().getData(), getSelectedItemId());
+
+ // // Build menu... always starts with the EDIT action...
+ // Intent[] specifics = new Intent[1];
+ // specifics[0] = new Intent(Intent.ACTION_EDIT, uri);
+ // MenuItem[] items = new MenuItem[1];
+
+ // // ... is followed by whatever other actions are available...
+ // Intent intent = new Intent(null, uri);
+ // intent.addCategory(Intent.CATEGORY_ALTERNATIVE);
+ // menu.addIntentOptions(Menu.CATEGORY_ALTERNATIVE, 0, 0, null, specifics, intent, 0,
+ // items);
+
+ // // Give a shortcut to the edit action.
+ // if (items[0] != null) {
+ // items[0].setShortcut('1', 'e');
+ // }
+ // } else {
+ // menu.removeGroup(Menu.CATEGORY_ALTERNATIVE);
+ // }
+
+ // return true;
+ // }
+
+ // @Override
+ // public boolean onOptionsItemSelected(MenuItem item) {
+ // switch (item.getItemId()) {
+ // case MENU_ITEM_INSERT:
+ // // Launch activity to insert a new item
+ // startActivity(new Intent(Intent.ACTION_INSERT, getIntent().getData()));
+ // return true;
+ // }
+ // return super.onOptionsItemSelected(item);
+ // }
+
+ @Override
+ public void onCreateContextMenu( ContextMenu menu, View view,
+ ContextMenuInfo menuInfo ) {
+ // AdapterView.AdapterContextMenuInfo info;
+ // try {
+ // info = (AdapterView.AdapterContextMenuInfo) menuInfo;
+ // } catch (ClassCastException e) {
+ // Log.e(TAG, "bad menuInfo", e);
+ // return;
+ // }
+
+ Utils.logf( "onCreateContextMenu called" );
+ MenuInflater inflater = getMenuInflater();
+ inflater.inflate( R.menu.games_list_item_menu, menu );
+
+ // Cursor cursor = (Cursor) getListAdapter().getItem(info.position);
+ // if (cursor == null) {
+ // // For some reason the requested item isn't available, do nothing
+ // return;
+ // }
+
+ // Setup the menu header
+ // menu.setHeaderTitle(cursor.getString(COLUMN_INDEX_TITLE));
+ }
+
+ private void doOpen() {
+ Intent intent = new Intent( Intent.ACTION_EDIT );
+ intent.setClassName( "org.eehouse.android.xw4",
+ "org.eehouse.android.xw4.BoardActivity");
+ startActivity( intent );
+ }
+
+ @Override
+ public boolean onContextItemSelected( MenuItem item ) {
+ boolean handled = false;
+ AdapterView.AdapterContextMenuInfo info;
+ try {
+ info = (AdapterView.AdapterContextMenuInfo) item.getMenuInfo();
+ } catch (ClassCastException e) {
+ Log.e(TAG, "bad menuInfo", e);
+ return false;
+ }
+
+ switch (item.getItemId()) {
+ case R.id.list_item_open:
+ doOpen();
+ handled = true;
+ break;
+ case R.id.list_item_view:
+ Utils.logf( "view" );
+ handled = true;
+ break;
+ case R.id.list_item_hide:
+ Utils.logf( "hide" );
+ handled = true;
+ break;
+ case R.id.list_item_delete:
+ Utils.logf( "delete" );
+ handled = true;
+ break;
+ }
+ return handled;
+ }
+
+ public void onClick( View v ) {
+ Intent intent = new Intent();
+ intent.setClassName( "org.eehouse.android.xw4",
+ "org.eehouse.android.xw4.GameConfig");
+ intent.setAction( Intent.ACTION_INSERT );
+ startActivity( intent );
+ }
+
+ @Override
+ protected void onListItemClick(ListView l, View v, int position, long id) {
+ doOpen();
+ }
+
+
+ static {
+ System.loadLibrary("xwjni");
+ }
+}
diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/Utils.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/Utils.java
new file mode 100644
index 000000000..786a58fbe
--- /dev/null
+++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/Utils.java
@@ -0,0 +1,20 @@
+
+package org.eehouse.android.xw4;
+
+import android.util.Log;
+import java.text.MessageFormat;
+
+public class Utils {
+ static final String TAG = "EJAVA";
+
+ private Utils() {}
+
+ public static void logf( String format ) {
+ Log.d( TAG, format );
+ } // logf
+
+ public static void logf( String format, Object[] args ) {
+ MessageFormat mfmt = new MessageFormat( format );
+ logf( mfmt.format( args ) );
+ } // logf
+}
diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWords4.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWords4.java
new file mode 100644
index 000000000..2b6f69859
--- /dev/null
+++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWords4.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.eehouse.android.xw4;
+
+import android.net.Uri;
+import android.provider.BaseColumns;
+
+/**
+ * Convenience definitions
+ */
+public final class XWords4 {
+ public static final String AUTHORITY = "org.eehouse.android.xw4";
+
+ // This class cannot be instantiated
+ private XWords4() {}
+
+ /**
+ * Notes table
+ */
+ public static final class Games implements BaseColumns {
+ // This class cannot be instantiated
+ private Games() {}
+
+ /**
+ * The content:// style URL for this table
+ */
+ public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/games");
+
+ /**
+ * The MIME type of {@link #CONTENT_URI} providing a directory of notes.
+ */
+ public static final String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.eehouse.org.xwgamesdir";
+
+ /**
+ * The MIME type of a {@link #CONTENT_URI} sub-directory of a single note.
+ */
+ public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/vnd.eehouse.org.xwgame";
+
+ /**
+ * The default sort order for this table
+ */
+ public static final String DEFAULT_SORT_ORDER = "modified DESC";
+
+ /**
+ * The title of the note
+ *
Type: TEXT
+ */
+ //public static final String TITLE = "title";
+
+ /**
+ * The note itself
+ *
Type: binary
+ */
+ public static final String DATA = "data";
+
+ /**
+ * The timestamp for when the game was created
+ *
Type: INTEGER (long from System.curentTimeMillis())
+ */
+ public static final String CREATED_DATE = "created";
+
+ /**
+ * The timestamp for when the game was last modified
+ *
Type: INTEGER (long from System.curentTimeMillis())
+ */
+ public static final String MODIFIED_DATE = "modified";
+ }
+}
diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/BoardHandler.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/BoardHandler.java
new file mode 100644
index 000000000..fe6098502
--- /dev/null
+++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/BoardHandler.java
@@ -0,0 +1,8 @@
+
+package org.eehouse.android.xw4.jni;
+
+public interface BoardHandler {
+
+ void startHandling( int gamePtr, CurGameInfo gi );
+
+}
diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/CommonPrefs.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/CommonPrefs.java
new file mode 100644
index 000000000..9272478fd
--- /dev/null
+++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/CommonPrefs.java
@@ -0,0 +1,15 @@
+package org.eehouse.android.xw4.jni;
+
+public class CommonPrefs {
+ public boolean showBoardArrow;
+ public boolean showRobotScores;
+ public boolean hideTileValues;
+ public boolean skipCommitConfirm;
+
+ public CommonPrefs() {
+ showBoardArrow = true;
+ showRobotScores = false;
+ hideTileValues = false;
+ skipCommitConfirm = true;
+ }
+}
diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/CurGameInfo.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/CurGameInfo.java
new file mode 100644
index 000000000..1dfa41f31
--- /dev/null
+++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/CurGameInfo.java
@@ -0,0 +1,40 @@
+
+package org.eehouse.android.xw4.jni;
+
+
+public class CurGameInfo {
+
+ private static final String BUILTIN_DICT = "OWL2_2to9.xwd";
+
+ public enum XWPhoniesChoice { PHONIES_IGNORE, PHONIES_WARN, PHONIES_DISALLOW };
+ public enum DeviceRole { SERVER_STANDALONE, SERVER_ISSERVER, SERVER_ISCLIENT };
+
+ public String dictName;
+ public LocalPlayer[] players;
+ public int gameID;
+ public int gameSeconds; /* for timer */
+ public int nPlayers;
+ public int boardSize;
+ public DeviceRole serverRole;
+
+ public boolean hintsNotAllowed;
+ public boolean timerEnabled;
+ public boolean allowPickTiles;
+ public boolean allowHintRect;
+ public int robotSmartness;
+ public XWPhoniesChoice phoniesAction;
+ public boolean confirmBTConnect; /* only used for BT */
+
+ public CurGameInfo() {
+ nPlayers = 3;
+ boardSize = 15;
+ players = new LocalPlayer[nPlayers];
+ serverRole = DeviceRole.SERVER_STANDALONE;
+ dictName = BUILTIN_DICT;
+ hintsNotAllowed = false;
+ players[0] = new LocalPlayer( "Eric", true );
+ players[1] = new LocalPlayer( "Kati" );
+ players[2] = new LocalPlayer( "Brynn", true );
+ }
+}
+
diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/DrawCtx.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/DrawCtx.java
new file mode 100644
index 000000000..16ab939e4
--- /dev/null
+++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/DrawCtx.java
@@ -0,0 +1,34 @@
+
+
+package org.eehouse.android.xw4.jni;
+
+import android.graphics.Rect;
+
+public interface DrawCtx {
+ static final int CELL_NONE = 0x00;
+ static final int CELL_ISBLANK = 0x01;
+ static final int CELL_HIGHLIGHT = 0x02;
+ static final int CELL_ISSTAR = 0x04;
+ static final int CELL_ISCURSOR = 0x08;
+ static final int CELL_ISEMPTY = 0x10; /* of a tray tile slot */
+ static final int CELL_VALHIDDEN = 0x20; /* show letter only, not value */
+ static final int CELL_DRAGSRC = 0x40; /* where drag originated */
+ static final int CELL_DRAGCUR = 0x80; /* where drag is now */
+ static final int CELL_ALL = 0xFF;
+
+ void measureRemText( Rect r, int nTilesLeft, int[] width, int[] height );
+ void measureScoreText( Rect r, DrawScoreInfo dsi, int[] width, int[] height );
+ void drawRemText( Rect rInner,Rect rOuter, int nTilesLeft, boolean focussed );
+ void score_drawPlayer( Rect rInner, Rect rOuter, DrawScoreInfo dsi );
+
+ boolean drawCell( Rect rect, String text, Object[] bitmaps, int tile,
+ int owner, int bonus, int hintAtts, int flags );
+
+ boolean trayBegin ( Rect rect, int owner, int dfs );
+ void drawTile( Rect rect, String text, Object[] bitmaps, int val, int flags );
+ void drawTileMidDrag ( Rect rect, String text, Object[] bitmaps,
+ int val, int owner, int flags );
+ void drawTileBack( Rect rect, int flags );
+ void drawTrayDivider( Rect rect, int flags );
+ void score_pendingScore( Rect rect, int score, int playerNum, int flags );
+}
diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/DrawScoreInfo.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/DrawScoreInfo.java
new file mode 100644
index 000000000..2a51cfcce
--- /dev/null
+++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/DrawScoreInfo.java
@@ -0,0 +1,28 @@
+
+package org.eehouse.android.xw4.jni;
+
+public class DrawScoreInfo {
+
+ public static final int CELL_NONE = 0x00;
+ public static final int CELL_ISBLANK = 0x01;
+ public static final int CELL_HIGHLIGHT = 0x02;
+ public static final int CELL_ISSTAR = 0x04;
+ public static final int CELL_ISCURSOR = 0x08;
+ public static final int CELL_ISEMPTY = 0x10; /* of a tray tile slot */
+ public static final int CELL_VALHIDDEN = 0x20; /* show letter only, not value */
+ public static final int CELL_DRAGSRC = 0x40; /* where drag originated */
+ public static final int CELL_DRAGCUR = 0x80; /* where drag is now */
+ public static final int CELL_ALL = 0xFF;
+
+ // LastScoreCallback lsc;
+ // void* lscClosure;
+ public String name;
+ public int playerNum;
+ public int totalScore;
+ public int nTilesLeft; /* < 0 means don't use */
+ public int flags; // was CellFlags; use CELL_ constants above
+ public boolean isTurn;
+ public boolean selected;
+ public boolean isRemote;
+ public boolean isRobot;
+};
diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/LocalPlayer.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/LocalPlayer.java
new file mode 100644
index 000000000..9b0e367d1
--- /dev/null
+++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/LocalPlayer.java
@@ -0,0 +1,23 @@
+
+package org.eehouse.android.xw4.jni;
+
+public class LocalPlayer {
+ public String name;
+ public String password;
+ public int secondsUsed;
+ public boolean isRobot;
+ public boolean isLocal;
+
+ public LocalPlayer( String nm ) {
+ isLocal = true;
+ isRobot = false;
+ name = nm;
+ password = "";
+ }
+
+ public LocalPlayer( String nm, boolean robot ) {
+ this( nm );
+ isRobot = robot;
+ }
+}
+
diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/TransportProcs.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/TransportProcs.java
new file mode 100644
index 000000000..5ad1b410a
--- /dev/null
+++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/TransportProcs.java
@@ -0,0 +1,5 @@
+
+package org.eehouse.android.xw4.jni;
+
+public interface TransportProcs {
+}
diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/XW_UtilCtxt.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/XW_UtilCtxt.java
new file mode 100644
index 000000000..57f69acbc
--- /dev/null
+++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/XW_UtilCtxt.java
@@ -0,0 +1,17 @@
+
+package org.eehouse.android.xw4.jni;
+
+public interface XW_UtilCtxt {
+ static final int BONUS_NONE = 0;
+ static final int BONUS_DOUBLE_LETTER = 1;
+ static final int BONUS_DOUBLE_WORD = 2;
+ static final int BONUS_TRIPLE_LETTER = 3;
+ static final int BONUS_TRIPLE_WORD = 4;
+
+ int getSquareBonus( int col, int row );
+
+ void setTimer( int why, int when, int handle );
+ void clearTimer( int why );
+ void requestTime();
+
+}
diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/XwJNI.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/XwJNI.java
new file mode 100644
index 000000000..b2b873ac4
--- /dev/null
+++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/XwJNI.java
@@ -0,0 +1,60 @@
+
+
+package org.eehouse.android.xw4.jni;
+
+
+// Collection of native methods
+public class XwJNI {
+
+ /* XW_TrayVisState enum */
+ public static final int TRAY_HIDDEN = 0;
+ public static final int TRAY_REVERSED = 1;
+ public static final int TRAY_REVEALED = 2;
+
+ // Methods not part of the common interface but necessitated by
+ // how java/jni work (or perhaps my limited understanding of it.)
+
+ // callback into jni from java when timer set here fires.
+ public static native boolean timerFired( int gamePtr, int why,
+ int when, int handle );
+
+ // Game methods
+ public static native int game_makeNewGame( CurGameInfo gi, XW_UtilCtxt util,
+ DrawCtx draw, int gameID, CommonPrefs cp,
+ TransportProcs procs, byte[] dict );
+ public static native void game_dispose( int gamePtr );
+
+ // Board methods
+
+ public static native void board_invalAll( int gamePtr );
+ public static native boolean board_draw( int gamePtr );
+ public static native void board_setPos( int gamePtr, int left, int top,
+ boolean lefty );
+ public static native void board_setScale( int gamePtr, int hscale, int vscale );
+ public static native boolean board_setShowColors( int gamePtr, boolean on );
+ public static native void board_setScoreboardLoc( int gamePtr, int left,
+ int top, int width,
+ int height,
+ boolean divideHorizontally );
+ public static native void board_setTrayLoc( int gamePtr, int left,
+ int top, int width,
+ int height, int minDividerWidth );
+ public static native boolean board_handlePenDown( int gamePtr, int xx, int yy,
+ boolean[] handled );
+ public static native boolean board_handlePenMove( int gamePtr, int xx, int yy );
+ public static native boolean board_handlePenUp( int gamePtr, int xx, int yy );
+
+ public static native boolean board_juggleTray( int gamePtr );
+ public static native int board_getTrayVisState( int gamePtr );
+ public static native boolean board_hideTray( int gamePtr );
+ public static native boolean board_showTray( int gamePtr );
+ public static native boolean board_commitTurn( int gamePtr );
+ public static native boolean board_flip( int gamePtr );
+ public static native boolean board_replaceTiles( int gamePtr );
+ public static native void board_resetEngine( int gamePtr );
+ public static native boolean board_requestHint( int gamePtr, boolean useTileLimits,
+ boolean[] workRemains );
+
+ public static native void server_handleUndo( int gamePtr );
+ public static native boolean server_do( int gamePtr );
+}