mirror of
git://xwords.git.sourceforge.net/gitroot/xwords/xwords
synced 2025-01-30 08:34:16 +01:00
Merge branch 'android_branch' into android_groups
Conflicts: xwords4/android/XWords4/res/values/strings.xml xwords4/android/XWords4/src/org/eehouse/android/xw4/DBHelper.java xwords4/android/XWords4/src/org/eehouse/android/xw4/GamesList.java xwords4/android/XWords4/src/org/eehouse/android/xw4/XWApp.java
This commit is contained in:
commit
c4e638bd84
35 changed files with 671 additions and 639 deletions
|
@ -129,8 +129,19 @@
|
|||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data android:scheme="http"
|
||||
android:host="eehouse.org" android:pathPrefix="/and" />
|
||||
android:host="@string/invite_host"
|
||||
android:pathPrefix="@string/invite_prefix"
|
||||
/>
|
||||
</intent-filter>
|
||||
|
||||
<!-- <intent-filter> -->
|
||||
<!-- <action android:name="android.intent.action.VIEW" /> -->
|
||||
<!-- <category android:name="android.intent.category.DEFAULT" /> -->
|
||||
<!-- <category android:name="android.intent.category.BROWSABLE" /> -->
|
||||
<!-- <data android:mimeType="*/*" -->
|
||||
<!-- android:scheme="content" -->
|
||||
<!-- /> -->
|
||||
<!-- </intent-filter> -->
|
||||
</activity>
|
||||
|
||||
<!-- downloading dicts -->
|
||||
|
|
|
@ -7,13 +7,18 @@
|
|||
<body>
|
||||
<b>Crosswords 4.4 beta 56 release</b>
|
||||
<ul>New with this release
|
||||
|
||||
|
||||
<li>Improve invitations: no more redirection through a website, and
|
||||
confirm before creating duplicate games</li>
|
||||
<li>(For sideloading users only) No more browser involvement in
|
||||
updates: app can launch the installer directly to update
|
||||
itself</li>
|
||||
<li>Remove notifications when their games are deleted</li>
|
||||
</ul>
|
||||
|
||||
<ul>Next up
|
||||
<li>Make invitations more reliable</li>
|
||||
<li>Upgrade app without involving the browser</li>
|
||||
<li>One more idea for improving invitations</li>
|
||||
<li>Allow grouping of games in collapsible user-defined categores: "Games with
|
||||
Kati", "Finished games", etc.</li>
|
||||
</ul>
|
||||
|
||||
<p>(The full changelog
|
||||
|
|
|
@ -30,7 +30,6 @@
|
|||
<string name="key_clr_bonushint">key_clr_bonushint</string>
|
||||
|
||||
<string name="key_relay_host">key_relay_host</string>
|
||||
<string name="key_redir_host">key_redir_host</string>
|
||||
<string name="key_relay_port">key_relay_port2</string>
|
||||
<string name="key_update_url">key_update_url</string>
|
||||
<string name="key_update_prerel">key_update_prerel</string>
|
||||
|
@ -103,9 +102,12 @@
|
|||
<!-- other -->
|
||||
<string name="default_host">eehouse.org</string>
|
||||
|
||||
<!-- <string name="default_host">10.0.2.2</string> -->
|
||||
<string name="invite_host">eehouse.org</string>
|
||||
<string name="invite_prefix">/and/</string>
|
||||
<string name="invite_mime">application/x-xwordsinvite</string>
|
||||
<!--string name="invite_mime">text/plain</string-->
|
||||
|
||||
<string name="dict_url">http://eehouse.org/and_wordlists</string>
|
||||
<string name="game_url_pathf">//%1$s/and</string>
|
||||
<string name="expl_update_url">Update checks URL</string>
|
||||
<string name="default_update_url">http://eehouse.org/xw4/info.py</string>
|
||||
|
||||
|
|
|
@ -1233,27 +1233,18 @@
|
|||
encodings for the greater-than and less-than symbols which
|
||||
are not legal in xml strings.)-->
|
||||
<string name="invite_htmf">\u003ca href=\"%1$s\"\u003ETap
|
||||
here\u003c/a\u003E (or the raw link below) to accept my invitation and
|
||||
here\u003c/a\u003E (or the full link below) to accept my invitation and
|
||||
join this game.
|
||||
\u003cbr \\\u003E
|
||||
\u003cbr \\\u003E
|
||||
(raw link: %1$s)
|
||||
\u003cbr \\\u003E
|
||||
\u003cbr \\\u003E
|
||||
\u003ca href=\"http://eehouse.org/market_redir.php\"\u003E Tap
|
||||
here\u003c/a\u003E (or the raw link below) to
|
||||
install Crosswords if you haven\'t already.
|
||||
\u003cbr \\\u003E
|
||||
\u003cbr \\\u003E
|
||||
(raw link: http://eehouse.org/market_redir.php)
|
||||
(full link: %1$s)
|
||||
</string>
|
||||
|
||||
<!-- This is the body of the text version of the invitation. A URL
|
||||
is created with parameters describing the game and
|
||||
substituted for "%1$s".-->
|
||||
<string name="invite_txtf">Play Crosswords? Join this game: %1$s
|
||||
. (But install Crosswords http://eehouse.org/market_redir.php
|
||||
first if you haven\'t.)</string>
|
||||
<string name="invite_txtf">Let\'s play Crosswords! Join this game:
|
||||
%1$s .</string>
|
||||
|
||||
<!-- When I've created the invitation, in text or html, I ask
|
||||
Android to launch an app that can send it, typically an email
|
||||
|
@ -1440,9 +1431,7 @@
|
|||
Guest wordlists; Host wins.</string>
|
||||
|
||||
|
||||
|
||||
<string name="downloading_dictf">Downloading Crosswords
|
||||
wordlist %s...</string>
|
||||
<string name="downloading_dictf">Downloading %s...</string>
|
||||
|
||||
<!--
|
||||
############################################################
|
||||
|
@ -1474,9 +1463,10 @@
|
|||
downloading and not opening the game. This first message
|
||||
takes wordlist name and language substituted in for %1$ and
|
||||
%2$ -->
|
||||
<string name="no_dictf">Unable to open game \"%1$s\" because no
|
||||
%2$s wordlist found. (It may have been deleted, or stored on
|
||||
an external card that is no longer available.)</string>
|
||||
<string name="no_dictf">You need to download a replacement %2$s
|
||||
wordlist before you can open game \"%1$s\". (The original may have
|
||||
been deleted or stored on an external card that is no longer
|
||||
available.)</string>
|
||||
|
||||
<!-- This is an alternative message presented when there's also
|
||||
the option of downloading another wordlist. Game name,
|
||||
|
@ -1564,8 +1554,8 @@
|
|||
the same room name over and over so they'll get this warning
|
||||
and it's harmless to ignore it. -->
|
||||
<string name="dup_game_queryf">You already have a game that seems
|
||||
to have been created from the same invitation. Are you sure you
|
||||
want to open another?</string>
|
||||
to have been created (on %1$s) from the same invitation. Are you
|
||||
sure you want to create another?</string>
|
||||
|
||||
<!-- Title of generic dialog used to display information -->
|
||||
<string name="info_title">FYI...</string>
|
||||
|
@ -2127,10 +2117,10 @@
|
|||
play Crosswords using the wordlist %2$s (for play in %3$s), but it
|
||||
is not installed. Would you like to download the wordlist or
|
||||
decline the invitation?</string>
|
||||
<string name="invite_dict_missing_body_nonamef">You have been invited to
|
||||
play Crosswords using the wordlist %2$s (for play in %3$s), but it
|
||||
is not installed. Would you like to download the wordlist or
|
||||
decline the invitation?</string>
|
||||
<string name="invite_dict_missing_body_nonamef">You have been
|
||||
invited to play Crosswords using the wordlist %2$s (for play in
|
||||
%3$s), but it is not installed. Would you like to download the
|
||||
wordlist?</string>
|
||||
<string name="button_decline">Decline</string>
|
||||
|
||||
<string name="downloadingf">Downloading %s...</string>
|
||||
|
|
|
@ -299,6 +299,11 @@
|
|||
android:summary="Menuitems etc."
|
||||
android:defaultValue="false"
|
||||
/>
|
||||
|
||||
<!-- For broken devices like my Blaze 4G that report a download
|
||||
directory that doesn't exist, allow users to set it. Mine:
|
||||
/sdcard/external_sd/download
|
||||
-->
|
||||
<org.eehouse.android.xw4.XWEditTextPreference
|
||||
android:key="@string/key_download_path"
|
||||
android:title="@string/download_path_title"
|
||||
|
@ -324,11 +329,6 @@
|
|||
android:defaultValue="10998"
|
||||
android:numeric="decimal"
|
||||
/>
|
||||
<org.eehouse.android.xw4.XWEditTextPreference
|
||||
android:key="@string/key_redir_host"
|
||||
android:title="@string/redir_host"
|
||||
android:defaultValue="@string/default_host"
|
||||
/>
|
||||
|
||||
<org.eehouse.android.xw4.XWEditTextPreference
|
||||
android:key="@string/key_dict_host"
|
||||
|
|
|
@ -444,7 +444,7 @@ public class BTService extends Service {
|
|||
result = BTCmd.INVITE_ACCPT;
|
||||
String body = Utils.format( BTService.this,
|
||||
R.string.new_bt_bodyf, sender );
|
||||
postNotification( gameID, R.string.new_bt_title, body );
|
||||
postNotification( gameID, R.string.new_bt_title, body, rowid );
|
||||
}
|
||||
} else {
|
||||
result = BTCmd.INVITE_DUPID;
|
||||
|
@ -497,7 +497,7 @@ public class BTService extends Service {
|
|||
buffer, addr,
|
||||
m_btMsgSink ) ) {
|
||||
postNotification( gameID, R.string.new_btmove_title,
|
||||
R.string.new_move_body );
|
||||
R.string.new_move_body, rowid );
|
||||
// do nothing
|
||||
} else {
|
||||
DbgUtils.logf( "nobody took msg for gameID %X",
|
||||
|
@ -967,17 +967,17 @@ public class BTService extends Service {
|
|||
return dos;
|
||||
}
|
||||
|
||||
private void postNotification( int gameID, int title, int body )
|
||||
private void postNotification( int gameID, int title, int body, long rowid )
|
||||
{
|
||||
postNotification( gameID, title, getString( body ) );
|
||||
postNotification( gameID, title, getString( body ), rowid );
|
||||
}
|
||||
|
||||
private void postNotification( int gameID, int title, String body )
|
||||
private void postNotification( int gameID, int title, String body,
|
||||
long rowid )
|
||||
{
|
||||
Intent intent = new Intent( this, DispatchNotify.class );
|
||||
intent.putExtra( DispatchNotify.GAMEID_EXTRA, gameID );
|
||||
Intent intent = GamesList.makeGameIDIntent( this, gameID );
|
||||
Utils.postNotification( this, intent, R.string.new_btmove_title,
|
||||
body, gameID );
|
||||
body, (int)rowid );
|
||||
}
|
||||
|
||||
private Thread killSocketIn( final BluetoothSocket socket )
|
||||
|
|
|
@ -57,7 +57,7 @@ import org.eehouse.android.xw4.jni.CurGameInfo.DeviceRole;
|
|||
|
||||
public class BoardActivity extends XWActivity
|
||||
implements TransportProcs.TPMsgHandler, View.OnClickListener,
|
||||
NetUtils.DownloadFinishedListener {
|
||||
DictImportActivity.DownloadFinishedListener {
|
||||
|
||||
public static final String INTENT_KEY_CHAT = "chat";
|
||||
|
||||
|
@ -167,7 +167,7 @@ public class BoardActivity extends XWActivity
|
|||
private boolean m_haveInvited = false;
|
||||
|
||||
private static BoardActivity s_this = null;
|
||||
private static Object s_thisLocker = new Object();
|
||||
private static Class s_thisLocker = BoardActivity.class;
|
||||
|
||||
public static boolean feedMessage( int gameID, byte[] msg,
|
||||
CommsAddrRec retAddr )
|
||||
|
@ -259,7 +259,8 @@ public class BoardActivity extends XWActivity
|
|||
if ( DLG_USEDICT == id ) {
|
||||
setGotGameDict( m_getDict );
|
||||
} else {
|
||||
NetUtils.downloadDictInBack( BoardActivity.this,
|
||||
DictImportActivity
|
||||
.downloadDictInBack( BoardActivity.this,
|
||||
m_gi.dictLang,
|
||||
m_getDict,
|
||||
BoardActivity.this );
|
||||
|
@ -1051,7 +1052,7 @@ public class BoardActivity extends XWActivity
|
|||
}
|
||||
|
||||
//////////////////////////////////////////////////
|
||||
// NetUtils.DownloadFinishedListener interface
|
||||
// DictImportActivity.DownloadFinishedListener interface
|
||||
//////////////////////////////////////////////////
|
||||
public void downloadFinished( final String name, final boolean success )
|
||||
{
|
||||
|
@ -1731,7 +1732,7 @@ public class BoardActivity extends XWActivity
|
|||
if ( null != m_xport ) {
|
||||
warnIfNoTransport();
|
||||
trySendChats();
|
||||
removeNotifications();
|
||||
Utils.cancelNotification( this, (int)m_rowid );
|
||||
m_xport.tickle( m_connType );
|
||||
tryInvites();
|
||||
}
|
||||
|
@ -1922,26 +1923,6 @@ public class BoardActivity extends XWActivity
|
|||
}
|
||||
}
|
||||
|
||||
private void removeNotifications()
|
||||
{
|
||||
int id = 0;
|
||||
switch( m_connType ) {
|
||||
case COMMS_CONN_BT:
|
||||
case COMMS_CONN_SMS:
|
||||
id = m_gi.gameID;
|
||||
break;
|
||||
case COMMS_CONN_RELAY:
|
||||
String relayID = DBUtils.getRelayID( this, m_rowid );
|
||||
if ( null != relayID ) {
|
||||
id = relayID.hashCode();
|
||||
}
|
||||
break;
|
||||
}
|
||||
if ( 0 != id ) {
|
||||
Utils.cancelNotification( this, id );
|
||||
}
|
||||
}
|
||||
|
||||
private void tryInvites()
|
||||
{
|
||||
if ( XWApp.BTSUPPORTED || XWApp.SMSSUPPORTED ) {
|
||||
|
|
|
@ -126,7 +126,7 @@ public class ConnStatusHandler {
|
|||
|
||||
private static HashMap<CommsConnType,SuccessRecord[]> s_records =
|
||||
new HashMap<CommsConnType,SuccessRecord[]>();
|
||||
private static Object s_lockObj = new Object();
|
||||
private static Class s_lockObj = ConnStatusHandler.class;
|
||||
private static boolean s_needsSave = false;
|
||||
|
||||
public static void setRect( int left, int top, int right, int bottom )
|
||||
|
|
|
@ -141,7 +141,7 @@ public class DBHelper extends SQLiteOpenHelper {
|
|||
|
||||
private static final String[] s_groupsSchema = {
|
||||
GROUPNAME, "TEXT"
|
||||
,EXPANDED, "INTEGER(0)"
|
||||
,EXPANDED, "INTEGER(1)"
|
||||
};
|
||||
|
||||
public DBHelper( Context context )
|
||||
|
|
|
@ -535,13 +535,16 @@ public class DBUtils {
|
|||
}
|
||||
}
|
||||
|
||||
public static long getRowIDForOpen( Context context, NetLaunchInfo nli )
|
||||
// Return creation time of newest game matching this nli, or null
|
||||
// if none found.
|
||||
public static Date getMostRecentCreate( Context context,
|
||||
NetLaunchInfo nli )
|
||||
{
|
||||
long result = ROWID_NOTFOUND;
|
||||
Date result = null;
|
||||
initDB( context );
|
||||
synchronized( s_dbHelper ) {
|
||||
SQLiteDatabase db = s_dbHelper.getReadableDatabase();
|
||||
String[] columns = { ROW_ID };
|
||||
String[] columns = { DBHelper.CREATE_TIME };
|
||||
String selection =
|
||||
String.format( "%s='%s' AND %s='%s' AND %s=%d AND %s=%d",
|
||||
DBHelper.ROOMNAME, nli.room,
|
||||
|
@ -549,9 +552,11 @@ public class DBUtils {
|
|||
DBHelper.DICTLANG, nli.lang,
|
||||
DBHelper.NUM_PLAYERS, nli.nPlayersT );
|
||||
Cursor cursor = db.query( DBHelper.TABLE_NAME_SUM, columns,
|
||||
selection, null, null, null, null );
|
||||
if ( 1 == cursor.getCount() && cursor.moveToFirst() ) {
|
||||
result = cursor.getLong( cursor.getColumnIndex(ROW_ID) );
|
||||
selection, null, null, null,
|
||||
DBHelper.CREATE_TIME + " DESC" ); // order by
|
||||
if ( cursor.moveToNext() ) {
|
||||
int indx = cursor.getColumnIndex( DBHelper.CREATE_TIME );
|
||||
result = new Date( cursor.getLong( indx ) );
|
||||
}
|
||||
cursor.close();
|
||||
db.close();
|
||||
|
@ -559,14 +564,14 @@ public class DBUtils {
|
|||
return result;
|
||||
}
|
||||
|
||||
public static long getRowIDForOpen( Context context, Uri data )
|
||||
public static Date getMostRecentCreate( Context context, Uri data )
|
||||
{
|
||||
long rowid = ROWID_NOTFOUND;
|
||||
NetLaunchInfo nli = new NetLaunchInfo( data );
|
||||
Date result = null;
|
||||
NetLaunchInfo nli = new NetLaunchInfo( context, data );
|
||||
if ( null != nli && nli.isValid() ) {
|
||||
rowid = getRowIDForOpen( context, nli );
|
||||
result = getMostRecentCreate( context, nli );
|
||||
}
|
||||
return rowid;
|
||||
return result;
|
||||
}
|
||||
|
||||
public static String[] getRelayIDs( Context context, boolean noMsgs )
|
||||
|
@ -704,11 +709,7 @@ public class DBUtils {
|
|||
{
|
||||
Assert.assertTrue( lock.canWrite() );
|
||||
long rowid = lock.getRowid();
|
||||
initDB( context );
|
||||
synchronized( s_dbHelper ) {
|
||||
SQLiteDatabase db = s_dbHelper.getWritableDatabase();
|
||||
|
||||
String selection = String.format( ROW_ID_FMT, rowid );
|
||||
ContentValues values = new ContentValues();
|
||||
values.put( DBHelper.SNAPSHOT, bytes );
|
||||
|
||||
|
@ -718,20 +719,10 @@ public class DBUtils {
|
|||
}
|
||||
values.put( DBHelper.LASTPLAY_TIME, timestamp );
|
||||
|
||||
int result = db.update( DBHelper.TABLE_NAME_SUM,
|
||||
values, selection, null );
|
||||
if ( 0 == result ) {
|
||||
Assert.fail();
|
||||
// values.put( DBHelper.FILE_NAME, path );
|
||||
// rowid = db.insert( DBHelper.TABLE_NAME_SUM, null, values );
|
||||
// DbgUtils.logf( "insert=>%d", rowid );
|
||||
// Assert.assertTrue( row >= 0 );
|
||||
}
|
||||
db.close();
|
||||
}
|
||||
setCached( rowid, null ); // force reread
|
||||
updateRow( context, DBHelper.TABLE_NAME_SUM, rowid, values );
|
||||
|
||||
if ( -1 != rowid ) {
|
||||
setCached( rowid, null ); // force reread
|
||||
if ( -1 != rowid ) { // Is this possible? PENDING
|
||||
notifyListeners( rowid );
|
||||
}
|
||||
return rowid;
|
||||
|
@ -1370,25 +1361,14 @@ public class DBUtils {
|
|||
private static void saveChatHistory( Context context, long rowid,
|
||||
String history )
|
||||
{
|
||||
initDB( context );
|
||||
synchronized( s_dbHelper ) {
|
||||
SQLiteDatabase db = s_dbHelper.getWritableDatabase();
|
||||
|
||||
String selection = String.format( ROW_ID_FMT, rowid );
|
||||
ContentValues values = new ContentValues();
|
||||
if ( null != history ) {
|
||||
values.put( DBHelper.CHAT_HISTORY, history );
|
||||
} else {
|
||||
values.putNull( DBHelper.CHAT_HISTORY );
|
||||
}
|
||||
|
||||
long timestamp = new Date().getTime();
|
||||
values.put( DBHelper.LASTPLAY_TIME, timestamp );
|
||||
|
||||
int result = db.update( DBHelper.TABLE_NAME_SUM,
|
||||
values, selection, null );
|
||||
db.close();
|
||||
}
|
||||
values.put( DBHelper.LASTPLAY_TIME, new Date().getTime() );
|
||||
updateRow( context, DBHelper.TABLE_NAME_SUM, rowid, values );
|
||||
}
|
||||
|
||||
private static void initDB( Context context )
|
||||
|
@ -1401,6 +1381,23 @@ public class DBUtils {
|
|||
}
|
||||
}
|
||||
|
||||
private static void updateRow( Context context, String table,
|
||||
long rowid, ContentValues values )
|
||||
{
|
||||
initDB( context );
|
||||
synchronized( s_dbHelper ) {
|
||||
SQLiteDatabase db = s_dbHelper.getWritableDatabase();
|
||||
|
||||
String selection = String.format( ROW_ID_FMT, rowid );
|
||||
|
||||
int result = db.update( table, values, selection, null );
|
||||
db.close();
|
||||
if ( 0 == result ) {
|
||||
DbgUtils.logf( "updateRow failed" );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void notifyListeners( long rowid )
|
||||
{
|
||||
synchronized( s_listeners ) {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
/* -*- compile-command: "cd ../../../../../; ant debug install"; -*- */
|
||||
/*
|
||||
* Copyright 2009-2010 by Eric House (xwords@eehouse.org). All
|
||||
* rights reserved.
|
||||
* Copyright 2009-2012 by Eric House (xwords@eehouse.org). All rights
|
||||
* reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
|
@ -21,6 +21,7 @@
|
|||
package org.eehouse.android.xw4;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.AsyncTask;
|
||||
|
@ -32,15 +33,37 @@ import java.io.File;
|
|||
import java.io.FileOutputStream;
|
||||
import java.io.InputStream;
|
||||
import java.net.URI;
|
||||
import java.security.MessageDigest;
|
||||
import java.util.HashMap;
|
||||
|
||||
import junit.framework.Assert;
|
||||
|
||||
public class DictImportActivity extends XWActivity {
|
||||
|
||||
public static final String APK_EXTRA = "APK";
|
||||
// URIs coming in in intents
|
||||
private static final String APK_EXTRA = "APK";
|
||||
private static final String DICT_EXTRA = "XWD";
|
||||
|
||||
public interface DownloadFinishedListener {
|
||||
void downloadFinished( String name, boolean success );
|
||||
}
|
||||
|
||||
// Track callbacks for downloads.
|
||||
private static class ListenerData {
|
||||
public ListenerData( String dictName, DownloadFinishedListener lstnr )
|
||||
{
|
||||
m_dictName = dictName;
|
||||
m_lstnr = lstnr;
|
||||
}
|
||||
public String m_dictName;
|
||||
public DownloadFinishedListener m_lstnr;
|
||||
}
|
||||
private static HashMap<String,ListenerData> s_listeners =
|
||||
new HashMap<String,ListenerData>();
|
||||
|
||||
private class DownloadFilesTask extends AsyncTask<Uri, Integer, Long> {
|
||||
private String m_saved = null;
|
||||
private String m_savedDict = null;
|
||||
private String m_url = null;
|
||||
private boolean m_isApp = false;
|
||||
private File m_appFile = null;
|
||||
|
||||
|
@ -50,10 +73,16 @@ public class DictImportActivity extends XWActivity {
|
|||
m_isApp = isApp;
|
||||
}
|
||||
|
||||
public DownloadFilesTask( String url, boolean isApp )
|
||||
{
|
||||
this( isApp );
|
||||
m_url = url;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Long doInBackground( Uri... uris )
|
||||
{
|
||||
m_saved = null;
|
||||
m_savedDict = null;
|
||||
m_appFile = null;
|
||||
|
||||
int count = uris.length;
|
||||
|
@ -71,7 +100,7 @@ public class DictImportActivity extends XWActivity {
|
|||
if ( m_isApp ) {
|
||||
m_appFile = saveToDownloads( is, name );
|
||||
} else {
|
||||
m_saved = saveDict( is, name );
|
||||
m_savedDict = saveDict( is, name );
|
||||
}
|
||||
is.close();
|
||||
} catch ( java.net.URISyntaxException use ) {
|
||||
|
@ -89,15 +118,19 @@ public class DictImportActivity extends XWActivity {
|
|||
protected void onPostExecute( Long result )
|
||||
{
|
||||
DbgUtils.logf( "onPostExecute passed %d", result );
|
||||
if ( null != m_saved ) {
|
||||
if ( null != m_savedDict ) {
|
||||
DictUtils.DictLoc loc =
|
||||
XWPrefs.getDefaultLoc( DictImportActivity.this );
|
||||
DictLangCache.inval( DictImportActivity.this, m_saved,
|
||||
DictLangCache.inval( DictImportActivity.this, m_savedDict,
|
||||
loc, true );
|
||||
callListener( m_url, true );
|
||||
} else if ( null != m_appFile ) {
|
||||
// launch the installer
|
||||
Intent intent = Utils.makeInstallIntent( m_appFile );
|
||||
startActivity( intent );
|
||||
} else {
|
||||
// we failed at something....
|
||||
callListener( m_url, false );
|
||||
}
|
||||
finish();
|
||||
}
|
||||
|
@ -120,24 +153,29 @@ public class DictImportActivity extends XWActivity {
|
|||
Uri uri = intent.getData();
|
||||
if ( null == uri ) {
|
||||
String url = intent.getStringExtra( APK_EXTRA );
|
||||
boolean isApp = null != url;
|
||||
if ( !isApp ) {
|
||||
url = intent.getStringExtra( DICT_EXTRA );
|
||||
}
|
||||
if ( null != url ) {
|
||||
dft = new DownloadFilesTask( true );
|
||||
dft = new DownloadFilesTask( url, isApp );
|
||||
uri = Uri.parse( url );
|
||||
}
|
||||
} else if ( null != intent.getType()
|
||||
&& intent.getType().equals( "application/x-xwordsdict" ) ) {
|
||||
dft = new DownloadFilesTask( false );
|
||||
} else if ( uri.toString().endsWith( XWConstants.DICT_EXTN ) ) {
|
||||
String txt = getString( R.string.downloading_dictf,
|
||||
basename( uri.getPath()) );
|
||||
TextView view = (TextView)findViewById( R.id.dwnld_message );
|
||||
view.setText( txt );
|
||||
dft = new DownloadFilesTask( false );
|
||||
dft = new DownloadFilesTask( uri.toString(), false );
|
||||
}
|
||||
|
||||
if ( null == dft ) {
|
||||
finish();
|
||||
} else {
|
||||
String showName = basename( uri.getPath() );
|
||||
String msg = getString( R.string.downloading_dictf, showName );
|
||||
TextView view = (TextView)findViewById( R.id.dwnld_message );
|
||||
view.setText( msg );
|
||||
|
||||
dft.execute( uri );
|
||||
}
|
||||
}
|
||||
|
@ -146,7 +184,6 @@ public class DictImportActivity extends XWActivity {
|
|||
{
|
||||
boolean success = false;
|
||||
File appFile = new File( DictUtils.getDownloadDir( this ), name );
|
||||
Assert.assertNotNull( appFile );
|
||||
|
||||
byte[] buf = new byte[1024*4];
|
||||
try {
|
||||
|
@ -183,6 +220,57 @@ public class DictImportActivity extends XWActivity {
|
|||
{
|
||||
return new File(path).getName();
|
||||
}
|
||||
|
||||
private static void rememberListener( String url, String name,
|
||||
DownloadFinishedListener lstnr )
|
||||
{
|
||||
ListenerData ld = new ListenerData( name, lstnr );
|
||||
synchronized( s_listeners ) {
|
||||
s_listeners.put( url, ld );
|
||||
}
|
||||
}
|
||||
|
||||
private static void callListener( String url, boolean success )
|
||||
{
|
||||
if ( null != url ) {
|
||||
ListenerData ld;
|
||||
synchronized( s_listeners ) {
|
||||
ld = s_listeners.get( url );
|
||||
if ( null != ld ) {
|
||||
s_listeners.remove( url );
|
||||
}
|
||||
}
|
||||
if ( null != ld ) {
|
||||
ld.m_lstnr.downloadFinished( ld.m_dictName, success );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void downloadDictInBack( Context context, int lang,
|
||||
String name,
|
||||
DownloadFinishedListener lstnr )
|
||||
{
|
||||
String url = Utils.makeDictUrl( context, lang, name );
|
||||
if ( null != lstnr ) {
|
||||
rememberListener( url, name, lstnr );
|
||||
}
|
||||
downloadDictInBack( context, url );
|
||||
}
|
||||
|
||||
public static void downloadDictInBack( Context context, String url )
|
||||
{
|
||||
Intent intent = new Intent( context, DictImportActivity.class );
|
||||
intent.putExtra( DICT_EXTRA, url );
|
||||
context.startActivity( intent );
|
||||
}
|
||||
|
||||
public static Intent makeAppDownloadIntent( Context context, String url )
|
||||
{
|
||||
Intent intent = new Intent( context, DictImportActivity.class );
|
||||
intent.putExtra( APK_EXTRA, url );
|
||||
return intent;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -61,7 +61,7 @@ import org.eehouse.android.xw4.DictUtils.DictLoc;
|
|||
public class DictsActivity extends ExpandableListActivity
|
||||
implements View.OnClickListener, XWListItem.DeleteCallback,
|
||||
MountEventReceiver.SDCardNotifiee, DlgDelegate.DlgClickNotify,
|
||||
NetUtils.DownloadFinishedListener {
|
||||
DictImportActivity.DownloadFinishedListener {
|
||||
|
||||
private static final String DICT_DOLAUNCH = "do_launch";
|
||||
private static final String DICT_LANG_EXTRA = "use_lang";
|
||||
|
@ -339,7 +339,8 @@ public class DictsActivity extends ExpandableListActivity
|
|||
String name = intent.getStringExtra( MultiService.DICT );
|
||||
m_launchedForMissing = true;
|
||||
m_handler = new Handler();
|
||||
NetUtils.downloadDictInBack( DictsActivity.this, lang,
|
||||
DictImportActivity
|
||||
.downloadDictInBack( DictsActivity.this, lang,
|
||||
name, DictsActivity.this );
|
||||
}
|
||||
};
|
||||
|
@ -555,10 +556,9 @@ public class DictsActivity extends ExpandableListActivity
|
|||
{
|
||||
int loci = intent.getIntExtra( UpdateCheckReceiver.NEW_DICT_LOC, 0 );
|
||||
if ( 0 < loci ) {
|
||||
DictLoc loc = DictLoc.values()[loci];
|
||||
String url =
|
||||
intent.getStringExtra( UpdateCheckReceiver.NEW_DICT_URL );
|
||||
NetUtils.downloadDictInBack( this, url, loc, null );
|
||||
DictImportActivity.downloadDictInBack( this, url );
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
@ -769,7 +769,7 @@ public class DictsActivity extends ExpandableListActivity
|
|||
launchAndDownload( activity, 0, null );
|
||||
}
|
||||
|
||||
// NetUtils.DownloadFinishedListener interface
|
||||
// DictImportActivity.DownloadFinishedListener interface
|
||||
public void downloadFinished( String name, final boolean success )
|
||||
{
|
||||
if ( m_launchedForMissing ) {
|
||||
|
|
|
@ -33,175 +33,16 @@ import org.eehouse.android.xw4.jni.GameSummary;
|
|||
|
||||
public class DispatchNotify extends Activity {
|
||||
|
||||
public static final String RELAYIDS_EXTRA = "relayids";
|
||||
public static final String GAMEID_EXTRA = "gameid";
|
||||
|
||||
public interface HandleRelaysIface {
|
||||
void handleRelaysIDs( final String[] relayIDs );
|
||||
void handleInvite( final Uri invite );
|
||||
void handleGameID( int gameID );
|
||||
}
|
||||
|
||||
private static HashSet<HandleRelaysIface> s_running =
|
||||
new HashSet<HandleRelaysIface>();
|
||||
private static HandleRelaysIface s_handler;
|
||||
|
||||
@Override
|
||||
protected void onCreate( Bundle savedInstanceState )
|
||||
{
|
||||
boolean mustLaunch = false;
|
||||
super.onCreate( savedInstanceState );
|
||||
|
||||
String[] relayIDs = getIntent().getStringArrayExtra( RELAYIDS_EXTRA );
|
||||
int gameID = getIntent().getIntExtra( GAMEID_EXTRA, -1 );
|
||||
Uri data = getIntent().getData();
|
||||
|
||||
if ( null != relayIDs ) {
|
||||
if ( !tryHandle( relayIDs ) ) {
|
||||
mustLaunch = true;
|
||||
}
|
||||
} else if ( -1 != gameID ) {
|
||||
if ( !tryHandle( gameID ) ) {
|
||||
mustLaunch = true;
|
||||
}
|
||||
} else if ( null != data ) { // relay invite redirected URL case
|
||||
NetLaunchInfo nli = new NetLaunchInfo( data );
|
||||
if ( null != nli && nli.isValid() ) {
|
||||
long rowid = DBUtils.getRowIDForOpen( this, nli );
|
||||
if ( DBUtils.ROWID_NOTFOUND == rowid ) {
|
||||
boolean haveDict;
|
||||
if ( null == nli.dict ) { // can only test for language support
|
||||
haveDict =
|
||||
0 < DictLangCache.getHaveLang( this,
|
||||
nli.lang ).length;
|
||||
} else {
|
||||
haveDict =
|
||||
DictLangCache.haveDict( this, nli.lang, nli.dict );
|
||||
}
|
||||
if ( haveDict ) {
|
||||
if ( !tryHandle( data ) ) {
|
||||
mustLaunch = true;
|
||||
}
|
||||
} else {
|
||||
Intent intent =
|
||||
MultiService.makeMissingDictIntent( this, nli );
|
||||
intent.putExtra( MultiService.OWNER,
|
||||
MultiService.OWNER_RELAY );
|
||||
// do we have gameID?
|
||||
MultiService.
|
||||
postMissingDictNotification( this, intent,
|
||||
nli.inviteID
|
||||
.hashCode() );
|
||||
}
|
||||
} else {
|
||||
DbgUtils.logf( "DispatchNotify: dropping duplicate invite" );
|
||||
GameSummary summary = DBUtils.getSummary( this, rowid );
|
||||
if ( null != summary ) {
|
||||
gameID = summary.gameID;
|
||||
if ( !tryHandle( gameID ) ) {
|
||||
mustLaunch = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( mustLaunch ) {
|
||||
DbgUtils.logf( "DispatchNotify: nothing running" );
|
||||
Intent intent = new Intent( this, GamesList.class );
|
||||
|
||||
// This combination of flags will bring an existing
|
||||
// GamesList instance to the front, killing any children
|
||||
// it has, or create a new one if none exists. Coupled
|
||||
// with a "standard" launchMode it seems to work, meaning
|
||||
// both that the app preserves its stack in normal use
|
||||
// (you can go to Home with a stack of activities and
|
||||
// return to the top activity on that stack if you
|
||||
// relaunch the app) and that when I launch from here the
|
||||
// stack gets nuked and we don't get a second GamesList
|
||||
// instance.
|
||||
|
||||
intent.setFlags( Intent.FLAG_ACTIVITY_CLEAR_TOP
|
||||
| Intent.FLAG_ACTIVITY_NEW_TASK );
|
||||
if ( null != relayIDs ) {
|
||||
intent.putExtra( RELAYIDS_EXTRA, relayIDs );
|
||||
} else if ( -1 != gameID ) {
|
||||
intent.putExtra( GAMEID_EXTRA, gameID );
|
||||
} else if ( null != data ) {
|
||||
intent.setData( data );
|
||||
} else {
|
||||
Assert.fail();
|
||||
}
|
||||
startActivity( intent );
|
||||
if ( null != data ) { // relay invite redirected URL case
|
||||
GamesList.openGame( this, data );
|
||||
}
|
||||
|
||||
finish();
|
||||
}
|
||||
|
||||
public static void SetRunning( Activity running )
|
||||
{
|
||||
if ( running instanceof HandleRelaysIface ) {
|
||||
s_running.add( (HandleRelaysIface)running );
|
||||
}
|
||||
}
|
||||
|
||||
public static void ClearRunning( Activity running )
|
||||
{
|
||||
if ( running instanceof HandleRelaysIface ) {
|
||||
s_running.remove( (HandleRelaysIface)running );
|
||||
}
|
||||
}
|
||||
|
||||
public static void SetRelayIDsHandler( HandleRelaysIface iface )
|
||||
{
|
||||
s_handler = iface;
|
||||
}
|
||||
|
||||
private static boolean tryHandle( Uri data )
|
||||
{
|
||||
boolean handled = false;
|
||||
if ( null != s_handler ) {
|
||||
// This means the GamesList activity is frontmost
|
||||
s_handler.handleInvite( data );
|
||||
handled = true;
|
||||
} else {
|
||||
for ( HandleRelaysIface iface : s_running ) {
|
||||
iface.handleInvite( data );
|
||||
handled = true;
|
||||
}
|
||||
}
|
||||
return handled;
|
||||
}
|
||||
|
||||
public static boolean tryHandle( String[] relayIDs )
|
||||
{
|
||||
boolean handled = false;
|
||||
if ( null != s_handler ) {
|
||||
// This means the GamesList activity is frontmost
|
||||
s_handler.handleRelaysIDs( relayIDs );
|
||||
handled = true;
|
||||
} else {
|
||||
for ( HandleRelaysIface iface : s_running ) {
|
||||
iface.handleRelaysIDs( relayIDs );
|
||||
handled = true;
|
||||
}
|
||||
}
|
||||
return handled;
|
||||
}
|
||||
|
||||
public static boolean tryHandle( int gameID )
|
||||
{
|
||||
boolean handled = false;
|
||||
if ( null != s_handler ) {
|
||||
// This means the GamesList activity is frontmost
|
||||
s_handler.handleGameID( gameID );
|
||||
handled = true;
|
||||
} else {
|
||||
for ( HandleRelaysIface iface : s_running ) {
|
||||
iface.handleGameID( gameID );
|
||||
handled = true;
|
||||
}
|
||||
}
|
||||
return handled;
|
||||
}
|
||||
} // onCreate
|
||||
}
|
||||
|
|
|
@ -56,7 +56,18 @@ public class GCMIntentService extends GCMBaseIntentService {
|
|||
@Override
|
||||
protected void onMessage( Context context, Intent intent )
|
||||
{
|
||||
String value = intent.getStringExtra( "msg" );
|
||||
String value;
|
||||
|
||||
value = intent.getStringExtra( "getMoves" );
|
||||
if ( null != value && Boolean.parseBoolean( value ) ) {
|
||||
RelayReceiver.RestartTimer( context, true );
|
||||
}
|
||||
value = intent.getStringExtra( "checkUpdates" );
|
||||
if ( null != value && Boolean.parseBoolean( value ) ) {
|
||||
UpdateCheckReceiver.checkVersions( context, true );
|
||||
}
|
||||
|
||||
value = intent.getStringExtra( "msg" );
|
||||
if ( null != value ) {
|
||||
String title = intent.getStringExtra( "title" );
|
||||
if ( null != title ) {
|
||||
|
@ -64,11 +75,6 @@ public class GCMIntentService extends GCMBaseIntentService {
|
|||
Utils.postNotification( context, null, title, value, code );
|
||||
}
|
||||
}
|
||||
|
||||
value = intent.getStringExtra( "getMoves" );
|
||||
if ( null != value && Boolean.parseBoolean( value ) ) {
|
||||
RelayReceiver.RestartTimer( context, true );
|
||||
}
|
||||
}
|
||||
|
||||
public static void init( Application app )
|
||||
|
|
|
@ -24,12 +24,16 @@ import android.app.Activity;
|
|||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.text.Html;
|
||||
import android.text.TextUtils;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.util.Arrays;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import android.text.Html;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import junit.framework.Assert;
|
||||
|
||||
|
@ -311,6 +315,7 @@ public class GameUtils {
|
|||
GameLock lock = new GameLock( rowid, true );
|
||||
if ( lock.tryLock() ) {
|
||||
tellDied( context, lock, informNow );
|
||||
Utils.cancelNotification( context, (int)rowid );
|
||||
DBUtils.deleteGame( context, lock );
|
||||
lock.unlock();
|
||||
success = true;
|
||||
|
@ -361,7 +366,8 @@ public class GameUtils {
|
|||
String[] dictNames = gi.dictNames();
|
||||
DictUtils.DictPairs pairs = DictUtils.openDicts( context, dictNames );
|
||||
if ( pairs.anyMissing( dictNames ) ) {
|
||||
DbgUtils.logf( "loadMakeGame() failing: dict unavailable" );
|
||||
DbgUtils.logf( "loadMakeGame() failing: dicts %s unavailable",
|
||||
TextUtils.join( ",", dictNames ) );
|
||||
} else {
|
||||
gamePtr = XwJNI.initJNI();
|
||||
|
||||
|
@ -555,11 +561,26 @@ public class GameUtils {
|
|||
Intent intent = new Intent();
|
||||
if ( choseEmail ) {
|
||||
intent.setAction( Intent.ACTION_SEND );
|
||||
intent.setType( "message/rfc822");
|
||||
String subject =
|
||||
Utils.format( context, R.string.invite_subjectf, room );
|
||||
intent.putExtra( Intent.EXTRA_SUBJECT, subject );
|
||||
intent.putExtra( Intent.EXTRA_TEXT, Html.fromHtml(message) );
|
||||
|
||||
File tmpdir = XWApp.ATTACH_SUPPORTED ?
|
||||
DictUtils.getDownloadDir( context ) : null;
|
||||
if ( null == tmpdir ) { // no attachment
|
||||
intent.setType( "message/rfc822");
|
||||
} else {
|
||||
intent.setType( context.getString( R.string.invite_mime ) );
|
||||
|
||||
File attach = makeJsonFor( tmpdir, room, inviteID, lang,
|
||||
dict, nPlayers );
|
||||
Uri uri = Uri.fromFile( attach );
|
||||
DbgUtils.logf( "using file uri for attachment: %s",
|
||||
uri.toString() );
|
||||
intent.putExtra( Intent.EXTRA_STREAM, uri );
|
||||
}
|
||||
|
||||
choiceID = R.string.invite_chooser_email;
|
||||
} else {
|
||||
intent.setAction( Intent.ACTION_VIEW );
|
||||
|
@ -724,7 +745,7 @@ public class GameUtils {
|
|||
CurGameInfo gi = new CurGameInfo( context );
|
||||
FeedUtilsImpl feedImpl = new FeedUtilsImpl( context, rowid );
|
||||
int gamePtr = loadMakeGame( context, gi, feedImpl, sink, lock );
|
||||
|
||||
if ( 0 != gamePtr ) {
|
||||
XwJNI.comms_resendAll( gamePtr, false, false );
|
||||
|
||||
if ( null != msgs ) {
|
||||
|
@ -745,6 +766,7 @@ public class GameUtils {
|
|||
draw = true;
|
||||
DBUtils.setMsgFlags( rowid, flags );
|
||||
}
|
||||
}
|
||||
lock.unlock();
|
||||
}
|
||||
return draw;
|
||||
|
@ -916,5 +938,31 @@ public class GameUtils {
|
|||
}
|
||||
}
|
||||
|
||||
private static File makeJsonFor( File dir, String room, String inviteID,
|
||||
int lang, String dict, int nPlayers )
|
||||
{
|
||||
File result = null;
|
||||
if ( XWApp.ATTACH_SUPPORTED ) {
|
||||
JSONObject json = new JSONObject();
|
||||
try {
|
||||
json.put( MultiService.ROOM, room );
|
||||
json.put( MultiService.INVITEID, inviteID );
|
||||
json.put( MultiService.LANG, lang );
|
||||
json.put( MultiService.DICT, dict );
|
||||
json.put( MultiService.NPLAYERST, nPlayers );
|
||||
byte[] data = json.toString().getBytes();
|
||||
|
||||
File file = new File( dir,
|
||||
String.format("invite_%s.json", room ) );
|
||||
FileOutputStream fos = new FileOutputStream( file );
|
||||
fos.write( data, 0, data.length );
|
||||
fos.close();
|
||||
result = file;
|
||||
} catch ( Exception ex ) {
|
||||
DbgUtils.loge( ex );
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -46,17 +46,18 @@ import android.widget.ExpandableListView;
|
|||
import android.widget.LinearLayout;
|
||||
import android.widget.ListView;
|
||||
import java.io.File;
|
||||
import java.util.Date;
|
||||
|
||||
// import android.telephony.PhoneStateListener;
|
||||
// import android.telephony.TelephonyManager;
|
||||
import junit.framework.Assert;
|
||||
|
||||
import org.eehouse.android.xw4.jni.*;
|
||||
|
||||
public class GamesList extends XWExpandableListActivity
|
||||
implements DispatchNotify.HandleRelaysIface,
|
||||
DBUtils.DBChangeListener,
|
||||
public class GamesList extends XWListActivity
|
||||
implements DBUtils.DBChangeListener,
|
||||
GameListAdapter.LoadItemCB,
|
||||
NetUtils.DownloadFinishedListener {
|
||||
DictImportActivity.DownloadFinishedListener {
|
||||
|
||||
private static final int WARN_NODICT = DlgDelegate.DIALOG_LAST + 1;
|
||||
private static final int WARN_NODICT_SUBST = WARN_NODICT + 1;
|
||||
|
@ -66,11 +67,15 @@ public class GamesList extends XWExpandableListActivity
|
|||
private static final int NEW_GROUP = WARN_NODICT + 5;
|
||||
private static final int RENAME_GROUP = WARN_NODICT + 6;
|
||||
private static final int CHANGE_GROUP = WARN_NODICT + 7;
|
||||
private static final int WARN_NODICT_NEW = WARN_NODICT + 8;
|
||||
|
||||
private static final String SAVE_ROWID = "SAVE_ROWID";
|
||||
private static final String SAVE_GROUPID = "SAVE_GROUPID";
|
||||
private static final String SAVE_DICTNAMES = "SAVE_DICTNAMES";
|
||||
|
||||
private static final String RELAYIDS_EXTRA = "relayids";
|
||||
private static final String GAMEID_EXTRA = "gameid";
|
||||
|
||||
private static final int NEW_NET_GAME_ACTION = 1;
|
||||
private static final int RESET_GAME_ACTION = 2;
|
||||
private static final int DELETE_GAME_ACTION = 3;
|
||||
|
@ -89,7 +94,7 @@ public class GamesList extends XWExpandableListActivity
|
|||
private GameListAdapter m_adapter;
|
||||
private String m_missingDict;
|
||||
private String m_missingDictName;
|
||||
private long m_missingDictRowId;
|
||||
private long m_missingDictRowId = DBUtils.ROWID_NOTFOUND;
|
||||
private String[] m_sameLangDicts;
|
||||
private int m_missingDictLang;
|
||||
private long m_rowid;
|
||||
|
@ -111,15 +116,17 @@ public class GamesList extends XWExpandableListActivity
|
|||
AlertDialog.Builder ab;
|
||||
switch ( id ) {
|
||||
case WARN_NODICT:
|
||||
case WARN_NODICT_NEW:
|
||||
case WARN_NODICT_SUBST:
|
||||
lstnr = new DialogInterface.OnClickListener() {
|
||||
public void onClick( DialogInterface dlg, int item ) {
|
||||
// just do one
|
||||
// no name, so user must pick
|
||||
if ( null == m_missingDictName ) {
|
||||
DictsActivity.launchAndDownload( GamesList.this,
|
||||
m_missingDictLang );
|
||||
} else {
|
||||
NetUtils.downloadDictInBack( GamesList.this,
|
||||
DictImportActivity
|
||||
.downloadDictInBack( GamesList.this,
|
||||
m_missingDictLang,
|
||||
m_missingDictName,
|
||||
GamesList.this );
|
||||
|
@ -133,6 +140,10 @@ public class GamesList extends XWExpandableListActivity
|
|||
if ( WARN_NODICT == id ) {
|
||||
message = getString( R.string.no_dictf,
|
||||
gameName, langName );
|
||||
} else if ( WARN_NODICT_NEW == id ) {
|
||||
message =
|
||||
getString( R.string.invite_dict_missing_body_nonamef,
|
||||
null, m_missingDictName, langName );
|
||||
} else {
|
||||
message = getString( R.string.no_dict_substf,
|
||||
gameName, m_missingDictName,
|
||||
|
@ -142,7 +153,7 @@ public class GamesList extends XWExpandableListActivity
|
|||
ab = new AlertDialog.Builder( this )
|
||||
.setTitle( R.string.no_dict_title )
|
||||
.setMessage( message )
|
||||
.setPositiveButton( R.string.button_ok, null )
|
||||
.setPositiveButton( R.string.button_cancel, null )
|
||||
.setNegativeButton( R.string.button_download, lstnr )
|
||||
;
|
||||
if ( WARN_NODICT_SUBST == id ) {
|
||||
|
@ -170,8 +181,7 @@ public class GamesList extends XWExpandableListActivity
|
|||
m_missingDictRowId,
|
||||
m_missingDictName,
|
||||
dict );
|
||||
GameUtils.launchGame( GamesList.this,
|
||||
m_missingDictRowId );
|
||||
launchGameIf();
|
||||
}
|
||||
};
|
||||
dialog = new AlertDialog.Builder( this )
|
||||
|
@ -355,8 +365,7 @@ public class GamesList extends XWExpandableListActivity
|
|||
{
|
||||
super.onNewIntent( intent );
|
||||
Assert.assertNotNull( intent );
|
||||
invalRelayIDs( intent.
|
||||
getStringArrayExtra( DispatchNotify.RELAYIDS_EXTRA ) );
|
||||
invalRelayIDs( intent.getStringArrayExtra( RELAYIDS_EXTRA ) );
|
||||
startFirstHasDict( intent );
|
||||
startNewNetGame( intent );
|
||||
startHasGameID( intent );
|
||||
|
@ -366,7 +375,6 @@ public class GamesList extends XWExpandableListActivity
|
|||
protected void onStart()
|
||||
{
|
||||
super.onStart();
|
||||
DispatchNotify.SetRelayIDsHandler( this );
|
||||
|
||||
boolean hide = CommonPrefs.getHideIntro( this );
|
||||
int hereOrGone = hide ? View.GONE : View.VISIBLE;
|
||||
|
@ -393,7 +401,6 @@ public class GamesList extends XWExpandableListActivity
|
|||
// (TelephonyManager)getSystemService( Context.TELEPHONY_SERVICE );
|
||||
// mgr.listen( m_phoneStateListener, PhoneStateListener.LISTEN_NONE );
|
||||
// m_phoneStateListener = null;
|
||||
DispatchNotify.SetRelayIDsHandler( null );
|
||||
|
||||
super.onStop();
|
||||
}
|
||||
|
@ -436,39 +443,6 @@ public class GamesList extends XWExpandableListActivity
|
|||
}
|
||||
}
|
||||
|
||||
// DispatchNotify.HandleRelaysIface interface
|
||||
public void handleRelaysIDs( final String[] relayIDs )
|
||||
{
|
||||
post( new Runnable() {
|
||||
public void run() {
|
||||
invalRelayIDs( relayIDs );
|
||||
startFirstHasDict( relayIDs );
|
||||
}
|
||||
} );
|
||||
}
|
||||
|
||||
public void handleInvite( Uri invite )
|
||||
{
|
||||
final NetLaunchInfo nli = new NetLaunchInfo( invite );
|
||||
if ( nli.isValid() ) {
|
||||
post( new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
startNewNetGame( nli );
|
||||
}
|
||||
} );
|
||||
}
|
||||
}
|
||||
|
||||
public void handleGameID( final int gameID )
|
||||
{
|
||||
post( new Runnable() {
|
||||
public void run() {
|
||||
startHasGameID( gameID );
|
||||
}
|
||||
} );
|
||||
}
|
||||
|
||||
// DBUtils.DBChangeListener interface
|
||||
public void gameSaved( final long rowid )
|
||||
{
|
||||
|
@ -538,8 +512,9 @@ public class GamesList extends XWExpandableListActivity
|
|||
if ( AlertDialog.BUTTON_POSITIVE == which ) {
|
||||
switch( id ) {
|
||||
case NEW_NET_GAME_ACTION:
|
||||
long rowid = GameUtils.makeNewNetGame( this, m_netLaunchInfo );
|
||||
GameUtils.launchGame( this, rowid, true );
|
||||
if ( checkWarnNoDict( m_netLaunchInfo ) ) {
|
||||
makeNewNetGameIf();
|
||||
}
|
||||
break;
|
||||
case RESET_GAME_ACTION:
|
||||
GameUtils.resetGame( this, m_rowid );
|
||||
|
@ -724,15 +699,21 @@ public class GamesList extends XWExpandableListActivity
|
|||
return handled;
|
||||
}
|
||||
|
||||
// NetUtils.DownloadFinishedListener interface
|
||||
// DictImportActivity.DownloadFinishedListener interface
|
||||
public void downloadFinished( String name, final boolean success )
|
||||
{
|
||||
post( new Runnable() {
|
||||
public void run() {
|
||||
boolean madeGame = false;
|
||||
if ( success ) {
|
||||
madeGame = makeNewNetGameIf() || launchGameIf();
|
||||
}
|
||||
if ( ! madeGame ) {
|
||||
int id = success ? R.string.download_done
|
||||
: R.string.download_failed;
|
||||
Utils.showToast( GamesList.this, id );
|
||||
}
|
||||
}
|
||||
} );
|
||||
}
|
||||
|
||||
|
@ -833,13 +814,38 @@ public class GamesList extends XWExpandableListActivity
|
|||
return handled;
|
||||
}
|
||||
|
||||
private boolean checkWarnNoDict( NetLaunchInfo nli )
|
||||
{
|
||||
// check that we have the dict required
|
||||
boolean haveDict;
|
||||
if ( null == nli.dict ) { // can only test for language support
|
||||
String[] dicts = DictLangCache.getHaveLang( this, nli.lang );
|
||||
haveDict = 0 < dicts.length;
|
||||
if ( haveDict ) {
|
||||
// Just pick one -- good enough for the period when
|
||||
// users aren't using new clients that include the
|
||||
// dict name.
|
||||
nli.dict = dicts[0];
|
||||
}
|
||||
} else {
|
||||
haveDict =
|
||||
DictLangCache.haveDict( this, nli.lang, nli.dict );
|
||||
}
|
||||
if ( !haveDict ) {
|
||||
m_netLaunchInfo = nli;
|
||||
m_missingDictLang = nli.lang;
|
||||
m_missingDictName = nli.dict;
|
||||
showDialog( WARN_NODICT_NEW );
|
||||
}
|
||||
return haveDict;
|
||||
}
|
||||
|
||||
private boolean checkWarnNoDict( long rowid )
|
||||
{
|
||||
String[][] missingNames = new String[1][];
|
||||
int[] missingLang = new int[1];
|
||||
boolean hasDicts = GameUtils.gameDictsHere( this, rowid,
|
||||
missingNames,
|
||||
missingLang );
|
||||
boolean hasDicts =
|
||||
GameUtils.gameDictsHere( this, rowid, missingNames, missingLang );
|
||||
if ( !hasDicts ) {
|
||||
m_missingDictLang = missingLang[0];
|
||||
if ( 0 < missingNames[0].length ) {
|
||||
|
@ -855,7 +861,7 @@ public class GamesList extends XWExpandableListActivity
|
|||
} else {
|
||||
String dict = DictLangCache.getHaveLang( this, m_missingDictLang)[0];
|
||||
GameUtils.replaceDicts( this, m_missingDictRowId, null, dict );
|
||||
GameUtils.launchGame( this, m_missingDictRowId );
|
||||
launchGameIf();
|
||||
}
|
||||
}
|
||||
return hasDicts;
|
||||
|
@ -899,8 +905,7 @@ public class GamesList extends XWExpandableListActivity
|
|||
private void startFirstHasDict( Intent intent )
|
||||
{
|
||||
if ( null != intent ) {
|
||||
String[] relayIDs =
|
||||
intent.getStringArrayExtra( DispatchNotify.RELAYIDS_EXTRA );
|
||||
String[] relayIDs = intent.getStringArrayExtra( RELAYIDS_EXTRA );
|
||||
startFirstHasDict( relayIDs );
|
||||
}
|
||||
}
|
||||
|
@ -910,33 +915,35 @@ public class GamesList extends XWExpandableListActivity
|
|||
startActivity( new Intent( this, NewGameActivity.class ) );
|
||||
}
|
||||
|
||||
private void startNewNetGame( NetLaunchInfo info )
|
||||
private void startNewNetGame( NetLaunchInfo nli )
|
||||
{
|
||||
long rowid = DBUtils.getRowIDForOpen( this, info );
|
||||
Date create = DBUtils.getMostRecentCreate( this, nli );
|
||||
|
||||
if ( DBUtils.ROWID_NOTFOUND == rowid ) {
|
||||
rowid = GameUtils.makeNewNetGame( this, info );
|
||||
GameUtils.launchGame( this, rowid, true );
|
||||
if ( null == create ) {
|
||||
if ( checkWarnNoDict( nli ) ) {
|
||||
makeNewNetGame( nli );
|
||||
}
|
||||
} else {
|
||||
String msg = getString( R.string.dup_game_queryf, info.room );
|
||||
m_netLaunchInfo = info;
|
||||
String msg = getString( R.string.dup_game_queryf,
|
||||
create.toString() );
|
||||
m_netLaunchInfo = nli;
|
||||
showConfirmThen( msg, NEW_NET_GAME_ACTION );
|
||||
}
|
||||
} // startNewNetGame
|
||||
|
||||
private void startNewNetGame( Intent intent )
|
||||
{
|
||||
NetLaunchInfo info = null;
|
||||
NetLaunchInfo nli = null;
|
||||
if ( MultiService.isMissingDictIntent( intent ) ) {
|
||||
info = new NetLaunchInfo( intent );
|
||||
nli = new NetLaunchInfo( intent );
|
||||
} else {
|
||||
Uri data = intent.getData();
|
||||
if ( null != data ) {
|
||||
info = new NetLaunchInfo( data );
|
||||
nli = new NetLaunchInfo( this, data );
|
||||
}
|
||||
}
|
||||
if ( null != info && info.isValid() ) {
|
||||
startNewNetGame( info );
|
||||
if ( null != nli && nli.isValid() ) {
|
||||
startNewNetGame( nli );
|
||||
}
|
||||
} // startNewNetGame
|
||||
|
||||
|
@ -950,7 +957,7 @@ public class GamesList extends XWExpandableListActivity
|
|||
|
||||
private void startHasGameID( Intent intent )
|
||||
{
|
||||
int gameID = intent.getIntExtra( DispatchNotify.GAMEID_EXTRA, 0 );
|
||||
int gameID = intent.getIntExtra( GAMEID_EXTRA, 0 );
|
||||
if ( 0 != gameID ) {
|
||||
startHasGameID( gameID );
|
||||
}
|
||||
|
@ -992,9 +999,65 @@ public class GamesList extends XWExpandableListActivity
|
|||
return dialog;
|
||||
}
|
||||
|
||||
private boolean makeNewNetGameIf()
|
||||
{
|
||||
boolean madeGame = null != m_netLaunchInfo;
|
||||
if ( madeGame ) {
|
||||
makeNewNetGame( m_netLaunchInfo );
|
||||
m_netLaunchInfo = null;
|
||||
}
|
||||
return madeGame;
|
||||
}
|
||||
|
||||
private boolean launchGameIf()
|
||||
{
|
||||
boolean madeGame = DBUtils.ROWID_NOTFOUND != m_missingDictRowId;
|
||||
if ( madeGame ) {
|
||||
GameUtils.launchGame( this, m_missingDictRowId );
|
||||
m_missingDictRowId = DBUtils.ROWID_NOTFOUND;
|
||||
}
|
||||
return madeGame;
|
||||
}
|
||||
|
||||
private void makeNewNetGame( NetLaunchInfo info )
|
||||
{
|
||||
long rowid = GameUtils.makeNewNetGame( this, info );
|
||||
GameUtils.launchGame( this, rowid, true );
|
||||
}
|
||||
|
||||
public static void onGameDictDownload( Context context, Intent intent )
|
||||
{
|
||||
intent.setClass( context, GamesList.class );
|
||||
context.startActivity( intent );
|
||||
}
|
||||
|
||||
private static Intent makeSelfIntent( Context context )
|
||||
{
|
||||
Intent intent = new Intent( context, GamesList.class );
|
||||
intent.setFlags( Intent.FLAG_ACTIVITY_CLEAR_TOP
|
||||
| Intent.FLAG_ACTIVITY_NEW_TASK );
|
||||
return intent;
|
||||
}
|
||||
|
||||
public static Intent makeRelayIdsIntent( Context context,
|
||||
String[] relayIDs )
|
||||
{
|
||||
Intent intent = makeSelfIntent( context );
|
||||
intent.putExtra( RELAYIDS_EXTRA, relayIDs );
|
||||
return intent;
|
||||
}
|
||||
|
||||
public static Intent makeGameIDIntent( Context context, int gameID )
|
||||
{
|
||||
Intent intent = makeSelfIntent( context );
|
||||
intent.putExtra( GAMEID_EXTRA, gameID );
|
||||
return intent;
|
||||
}
|
||||
|
||||
public static void openGame( Context context, Uri data )
|
||||
{
|
||||
Intent intent = makeSelfIntent( context );
|
||||
intent.setData( data );
|
||||
context.startActivity( intent );
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,12 +20,15 @@
|
|||
|
||||
package org.eehouse.android.xw4;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri.Builder;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import java.net.URLEncoder;
|
||||
import java.io.InputStream;
|
||||
import org.json.JSONObject;
|
||||
import junit.framework.Assert;
|
||||
|
||||
|
||||
public class NetLaunchInfo {
|
||||
|
@ -64,11 +67,27 @@ public class NetLaunchInfo {
|
|||
m_valid = bundle.getBoolean( VALID );
|
||||
}
|
||||
|
||||
public NetLaunchInfo( Uri data )
|
||||
public NetLaunchInfo( Context context, Uri data )
|
||||
{
|
||||
m_valid = false;
|
||||
if ( null != data ) {
|
||||
String scheme = data.getScheme();
|
||||
try {
|
||||
if ( "content".equals(scheme) ) {
|
||||
Assert.assertNotNull( context );
|
||||
ContentResolver resolver = context.getContentResolver();
|
||||
InputStream is = resolver.openInputStream( data );
|
||||
int len = is.available();
|
||||
byte[] buf = new byte[len];
|
||||
is.read( buf );
|
||||
|
||||
JSONObject json = new JSONObject( new String( buf ) );
|
||||
room = json.getString( MultiService.ROOM );
|
||||
inviteID = json.getString( MultiService.INVITEID );
|
||||
lang = json.getInt( MultiService.LANG );
|
||||
dict = json.getString( MultiService.DICT );
|
||||
nPlayersT = json.getInt( MultiService.NPLAYERST );
|
||||
} else {
|
||||
room = data.getQueryParameter( "room" );
|
||||
inviteID = data.getQueryParameter( "id" );
|
||||
dict = data.getQueryParameter( "wl" );
|
||||
|
@ -76,6 +95,7 @@ public class NetLaunchInfo {
|
|||
lang = Integer.decode( langStr );
|
||||
String np = data.getQueryParameter( "np" );
|
||||
nPlayersT = Integer.decode( np );
|
||||
}
|
||||
m_valid = true;
|
||||
} catch ( Exception e ) {
|
||||
DbgUtils.logf( "unable to parse \"%s\"", data.toString() );
|
||||
|
@ -99,15 +119,15 @@ public class NetLaunchInfo {
|
|||
String inviteID, int lang,
|
||||
String dict, int nPlayersT )
|
||||
{
|
||||
Builder ub = new Builder();
|
||||
ub.scheme( "http" );
|
||||
ub.path( context.getString( R.string.game_url_pathf,
|
||||
XWPrefs.getDefaultRedirHost( context ) ) );
|
||||
|
||||
ub.appendQueryParameter( "lang", String.format("%d", lang ) );
|
||||
ub.appendQueryParameter( "np", String.format( "%d", nPlayersT ) );
|
||||
ub.appendQueryParameter( "room", room );
|
||||
ub.appendQueryParameter( "id", inviteID );
|
||||
Uri.Builder ub = new Uri.Builder()
|
||||
.scheme( "http" )
|
||||
.path( String.format( "//%s%s",
|
||||
context.getString(R.string.invite_host),
|
||||
context.getString(R.string.invite_prefix) ) )
|
||||
.appendQueryParameter( "lang", String.format("%d", lang ) )
|
||||
.appendQueryParameter( "np", String.format( "%d", nPlayersT ) )
|
||||
.appendQueryParameter( "room", room )
|
||||
.appendQueryParameter( "id", inviteID );
|
||||
if ( null != dict ) {
|
||||
ub.appendQueryParameter( "wl", dict );
|
||||
}
|
||||
|
|
|
@ -21,17 +21,11 @@
|
|||
package org.eehouse.android.xw4;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Handler;
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.InetAddress;
|
||||
import java.net.Socket;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
|
@ -50,10 +44,6 @@ public class NetUtils {
|
|||
public static byte PRX_GET_MSGS = 4;
|
||||
public static byte PRX_PUT_MSGS = 5;
|
||||
|
||||
public interface DownloadFinishedListener {
|
||||
void downloadFinished( String name, boolean success );
|
||||
}
|
||||
|
||||
public static Socket makeProxySocket( Context context,
|
||||
int timeoutMillis )
|
||||
{
|
||||
|
@ -273,68 +263,4 @@ public class NetUtils {
|
|||
DbgUtils.logf( "sendToRelay: null msgs" );
|
||||
}
|
||||
} // sendToRelay
|
||||
|
||||
static void downloadDictInBack( Context context, int lang, String name,
|
||||
DownloadFinishedListener lstnr )
|
||||
{
|
||||
DictUtils.DictLoc loc = XWPrefs.getDefaultLoc( context );
|
||||
downloadDictInBack( context, lang, name, loc, lstnr );
|
||||
}
|
||||
|
||||
static void downloadDictInBack( Context context, int lang, String name,
|
||||
DictUtils.DictLoc loc,
|
||||
DownloadFinishedListener lstnr )
|
||||
{
|
||||
String url = Utils.makeDictUrl( context, lang, name );
|
||||
downloadDictInBack( context, url, loc, lstnr );
|
||||
}
|
||||
|
||||
static void downloadDictInBack( final Context context, final String urlStr,
|
||||
final DictUtils.DictLoc loc,
|
||||
final DownloadFinishedListener lstnr )
|
||||
{
|
||||
String tmp = Utils.dictFromURL( context, urlStr );
|
||||
final String name = DictUtils.removeDictExtn( tmp );
|
||||
String msg = context.getString( R.string.downloadingf, name );
|
||||
final StatusNotifier sno =
|
||||
new StatusNotifier( context, msg, R.string.download_done );
|
||||
|
||||
new Thread( new Runnable() {
|
||||
public void run() {
|
||||
boolean success = false;
|
||||
HttpURLConnection urlConn = null;
|
||||
try {
|
||||
URL url = new URL( urlStr );
|
||||
urlConn = (HttpURLConnection)url.openConnection();
|
||||
InputStream in = new
|
||||
BufferedInputStream( urlConn.getInputStream(),
|
||||
1024*8 );
|
||||
success = DictUtils.saveDict( context, in,
|
||||
name, loc );
|
||||
|
||||
} catch ( java.net.MalformedURLException mue ) {
|
||||
DbgUtils.loge( mue );
|
||||
} catch ( java.io.IOException ioe ) {
|
||||
DbgUtils.loge( ioe );
|
||||
} catch ( Exception ce ) {
|
||||
// E.g. java.net.ConnectException; we failed
|
||||
// to download, ok.
|
||||
} finally {
|
||||
if ( null != urlConn ) {
|
||||
urlConn.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
sno.close();
|
||||
|
||||
if ( success ) {
|
||||
DictLangCache.inval( context, name, loc, true );
|
||||
}
|
||||
if ( null != lstnr ) {
|
||||
lstnr.downloadFinished( name, success );
|
||||
}
|
||||
}
|
||||
} ).start();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -63,13 +63,13 @@ public class RelayService extends Service {
|
|||
long[] rowids = DBUtils.getRowIDsFor( this, relayID );
|
||||
if ( null != rowids ) {
|
||||
for ( long rowid : rowids ) {
|
||||
Intent intent = new Intent( this, DispatchNotify.class );
|
||||
intent.putExtra( DispatchNotify.RELAYIDS_EXTRA,
|
||||
Intent intent =
|
||||
GamesList.makeRelayIdsIntent( this,
|
||||
new String[] {relayID} );
|
||||
String msg = Utils.format( this, R.string.notify_bodyf,
|
||||
GameUtils.getName( this, rowid ) );
|
||||
Utils.postNotification( this, intent, R.string.notify_title,
|
||||
msg, relayID.hashCode() );
|
||||
msg, (int)rowid );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -112,9 +112,7 @@ public class RelayService extends Service {
|
|||
if ( 0 < idsWMsgs.size() ) {
|
||||
String[] relayIDs = new String[idsWMsgs.size()];
|
||||
idsWMsgs.toArray( relayIDs );
|
||||
// if ( !DispatchNotify.tryHandle( relayIDs ) ) {
|
||||
setupNotification( relayIDs );
|
||||
// }
|
||||
}
|
||||
sink.send( this );
|
||||
}
|
||||
|
|
|
@ -388,7 +388,6 @@ public class SMSService extends Service {
|
|||
int count = (msg.length() + (MAX_LEN_TEXT-1)) / MAX_LEN_TEXT;
|
||||
String[] result = new String[count];
|
||||
int msgID = ++s_nSent % 0x000000FF;
|
||||
DbgUtils.logf( "preparing %d packets for msgid %x", count, msgID );
|
||||
|
||||
int start = 0;
|
||||
int end = 0;
|
||||
|
@ -400,7 +399,6 @@ public class SMSService extends Service {
|
|||
end += len;
|
||||
result[ii] = String.format( "0:%X:%X:%X:%s", msgID, ii, count,
|
||||
msg.substring( start, end ) );
|
||||
DbgUtils.logf( "fragment[%d]: %s", ii, result[ii] );
|
||||
start = end;
|
||||
}
|
||||
return result;
|
||||
|
@ -432,7 +430,8 @@ public class SMSService extends Service {
|
|||
MultiService.OWNER_SMS );
|
||||
intent.putExtra( MultiService.INVITER,
|
||||
Utils.phoneToContact( this, phone, true ) );
|
||||
MultiService.postMissingDictNotification( this, intent, gameID );
|
||||
MultiService.postMissingDictNotification( this, intent,
|
||||
gameID );
|
||||
}
|
||||
break;
|
||||
case DATA:
|
||||
|
@ -504,7 +503,6 @@ public class SMSService extends Service {
|
|||
|
||||
private void disAssemble( String senderPhone, String fullMsg )
|
||||
{
|
||||
DbgUtils.logf( "disAssemble()" );
|
||||
byte[] data = XwJNI.base64Decode( fullMsg );
|
||||
DataInputStream dis =
|
||||
new DataInputStream( new ByteArrayInputStream(data) );
|
||||
|
@ -542,7 +540,7 @@ public class SMSService extends Service {
|
|||
String owner = Utils.phoneToContact( this, phone, true );
|
||||
String body = Utils.format( this, R.string.new_name_bodyf,
|
||||
owner );
|
||||
postNotification( gameID, R.string.new_sms_title, body );
|
||||
postNotification( gameID, R.string.new_sms_title, body, rowid );
|
||||
|
||||
ackInvite( phone, gameID );
|
||||
}
|
||||
|
@ -563,8 +561,6 @@ public class SMSService extends Service {
|
|||
for ( String fragment : fragments ) {
|
||||
String asPublic = toPublicFmt( fragment );
|
||||
mgr.sendTextMessage( phone, null, asPublic, sent, delivery );
|
||||
DbgUtils.logf( "Message \"%s\" of %d bytes sent to %s.",
|
||||
asPublic, asPublic.length(), phone );
|
||||
}
|
||||
if ( s_showToasts ) {
|
||||
DbgUtils.showf( this, "sent %dth msg", s_nSent );
|
||||
|
@ -607,19 +603,19 @@ public class SMSService extends Service {
|
|||
if ( GameUtils.feedMessage( this, rowid, msg, addr,
|
||||
sink ) ) {
|
||||
postNotification( gameID, R.string.new_smsmove_title,
|
||||
getString(R.string.new_move_body)
|
||||
);
|
||||
getString(R.string.new_move_body),
|
||||
rowid );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void postNotification( int gameID, int title, String body )
|
||||
private void postNotification( int gameID, int title, String body,
|
||||
long rowid )
|
||||
{
|
||||
Intent intent = new Intent( this, DispatchNotify.class );
|
||||
intent.putExtra( DispatchNotify.GAMEID_EXTRA, gameID );
|
||||
Utils.postNotification( this, intent, title, body, gameID );
|
||||
Intent intent = GamesList.makeGameIDIntent( this, gameID );
|
||||
Utils.postNotification( this, intent, title, body, (int)rowid );
|
||||
}
|
||||
|
||||
// Runs in separate thread
|
||||
|
@ -664,11 +660,9 @@ public class SMSService extends Service {
|
|||
@Override
|
||||
public void onReceive(Context arg0, Intent arg1)
|
||||
{
|
||||
DbgUtils.logf( "got MSG_DELIVERED" );
|
||||
switch ( getResultCode() ) {
|
||||
case Activity.RESULT_OK:
|
||||
sendResult( MultiEvent.SMS_SEND_OK );
|
||||
DbgUtils.logf( "SUCCESS!!!" );
|
||||
break;
|
||||
case SmsManager.RESULT_ERROR_RADIO_OFF:
|
||||
DbgUtils.showf( SMSService.this, "NO RADIO!!!" );
|
||||
|
@ -688,7 +682,6 @@ public class SMSService extends Service {
|
|||
@Override
|
||||
public void onReceive(Context arg0, Intent arg1)
|
||||
{
|
||||
DbgUtils.logf( "got MSG_DELIVERED" );
|
||||
if ( Activity.RESULT_OK == getResultCode() ) {
|
||||
DbgUtils.logf( "SUCCESS!!!" );
|
||||
} else {
|
||||
|
@ -709,7 +702,6 @@ public class SMSService extends Service {
|
|||
public int transportSend( byte[] buf, final CommsAddrRec addr, int gameID )
|
||||
{
|
||||
int nSent = -1;
|
||||
DbgUtils.logf( "SMSMsgSink.transportSend()" );
|
||||
if ( null != addr ) {
|
||||
nSent = sendPacket( addr.sms_phone, gameID, buf );
|
||||
} else {
|
||||
|
@ -752,7 +744,6 @@ public class SMSService extends Service {
|
|||
public boolean isComplete()
|
||||
{
|
||||
boolean complete = m_msgs.length == m_haveCount;
|
||||
DbgUtils.logf( "isComplete(msg %d)=>%b", m_msgID, complete );
|
||||
return complete;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,58 +0,0 @@
|
|||
/* -*- compile-command: "cd ../../../../../; ant debug install"; -*- */
|
||||
/*
|
||||
* Copyright 2012 by Eric House (xwords@eehouse.org). All rights
|
||||
* reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation; either version 2 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
*/
|
||||
|
||||
package org.eehouse.android.xw4;
|
||||
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
|
||||
public class StatusNotifier {
|
||||
private int m_id;
|
||||
private NotificationManager m_mgr;
|
||||
private Context m_context;
|
||||
|
||||
public StatusNotifier( Context context, String msg, int id )
|
||||
{
|
||||
m_context = context;
|
||||
m_id = id;
|
||||
|
||||
Notification notification =
|
||||
new Notification( R.drawable.icon48x48, msg,
|
||||
System.currentTimeMillis() );
|
||||
notification.flags = notification.flags |= Notification.FLAG_AUTO_CANCEL;
|
||||
PendingIntent pi = PendingIntent.getActivity( context, 0,
|
||||
new Intent(), 0 );
|
||||
notification.setLatestEventInfo( context, "", "", pi );
|
||||
|
||||
m_mgr = (NotificationManager)
|
||||
context.getSystemService( Context.NOTIFICATION_SERVICE );
|
||||
m_mgr.notify( id, notification );
|
||||
}
|
||||
|
||||
// Will likely be called from background thread
|
||||
public void close()
|
||||
{
|
||||
m_mgr.cancel( m_id );
|
||||
}
|
||||
|
||||
}
|
|
@ -197,9 +197,8 @@ public class UpdateCheckReceiver extends BroadcastReceiver {
|
|||
intent = new Intent( Intent.ACTION_VIEW,
|
||||
Uri.parse(url) );
|
||||
} else {
|
||||
intent = new Intent( context,
|
||||
DictImportActivity.class );
|
||||
intent.putExtra( DictImportActivity.APK_EXTRA, url );
|
||||
intent = DictImportActivity
|
||||
.makeAppDownloadIntent( context, url );
|
||||
}
|
||||
|
||||
String title =
|
||||
|
|
|
@ -174,7 +174,8 @@ public class Utils {
|
|||
}
|
||||
|
||||
public static void postNotification( Context context, Intent intent,
|
||||
String title, String body, int id )
|
||||
String title, String body,
|
||||
int id )
|
||||
{
|
||||
/* s_nextCode: per this link
|
||||
http://stackoverflow.com/questions/10561419/scheduling-more-than-one-pendingintent-to-same-activity-using-alarmmanager
|
||||
|
@ -425,7 +426,8 @@ public class Utils {
|
|||
|
||||
public static Intent makeInstallIntent( File file )
|
||||
{
|
||||
Uri uri = Uri.parse( "file:/" + file.getPath() );
|
||||
String withScheme = "file://" + file.getPath();
|
||||
Uri uri = Uri.parse( withScheme );
|
||||
Intent intent = new Intent( Intent.ACTION_VIEW );
|
||||
intent.setDataAndType( uri, XWConstants.APK_TYPE );
|
||||
intent.addFlags( Intent.FLAG_ACTIVITY_NEW_TASK );
|
||||
|
@ -442,7 +444,6 @@ public class Utils {
|
|||
pm.queryIntentActivities( intent,
|
||||
PackageManager.MATCH_DEFAULT_ONLY );
|
||||
result = 0 < doers.size();
|
||||
DbgUtils.logf( "canInstall()=>%b", result );
|
||||
return result;
|
||||
}
|
||||
|
||||
|
|
|
@ -48,7 +48,6 @@ public class XWActivity extends Activity
|
|||
{
|
||||
DbgUtils.logf( "%s.onStart(this=%H)", getClass().getName(), this );
|
||||
super.onStart();
|
||||
DispatchNotify.SetRunning( this );
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -73,7 +72,6 @@ public class XWActivity extends Activity
|
|||
protected void onStop()
|
||||
{
|
||||
DbgUtils.logf( "%s.onStop(this=%H)", getClass().getName(), this );
|
||||
DispatchNotify.ClearRunning( this );
|
||||
super.onStop();
|
||||
}
|
||||
|
||||
|
|
|
@ -32,6 +32,7 @@ public class XWApp extends Application {
|
|||
public static final boolean BTSUPPORTED = false;
|
||||
public static final boolean SMSSUPPORTED = true;
|
||||
public static final boolean GCMSUPPORTED = true;
|
||||
public static final boolean ATTACH_SUPPORTED = false;
|
||||
public static final boolean DEBUG = true;
|
||||
|
||||
public static final String SMS_PUBLIC_HEADER = "-XW4";
|
||||
|
|
|
@ -45,7 +45,6 @@ public class XWListActivity extends ListActivity
|
|||
{
|
||||
DbgUtils.logf( "%s.onStart(this=%H)", getClass().getName(), this );
|
||||
super.onStart();
|
||||
DispatchNotify.SetRunning( this );
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -70,7 +69,6 @@ public class XWListActivity extends ListActivity
|
|||
protected void onStop()
|
||||
{
|
||||
DbgUtils.logf( "%s.onStop(this=%H)", getClass().getName(), this );
|
||||
DispatchNotify.ClearRunning( this );
|
||||
super.onStop();
|
||||
}
|
||||
|
||||
|
|
|
@ -45,11 +45,6 @@ public class XWPrefs {
|
|||
return getPrefsString( context, R.string.key_relay_host );
|
||||
}
|
||||
|
||||
public static String getDefaultRedirHost( Context context )
|
||||
{
|
||||
return getPrefsString( context, R.string.key_redir_host );
|
||||
}
|
||||
|
||||
public static int getDefaultRelayPort( Context context )
|
||||
{
|
||||
String val = getPrefsString( context, R.string.key_relay_port );
|
||||
|
|
106
xwords4/android/scripts/and_index.php
Normal file
106
xwords4/android/scripts/and_index.php
Normal file
|
@ -0,0 +1,106 @@
|
|||
<?php
|
||||
|
||||
$g_androidStrings = array( "android", );
|
||||
$g_apk = 'XWords4-release_android_beta_55-39-gbffb231.apk';
|
||||
|
||||
function printHead() {
|
||||
print <<<EOF
|
||||
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" type="text/css" href="/xw4mobile.css" />
|
||||
<title>Crosswords Invite redirect</title>
|
||||
</head>
|
||||
<body>
|
||||
<div class="center">
|
||||
<img class="center" src="../icon48x48.png"/>
|
||||
</div>
|
||||
EOF;
|
||||
}
|
||||
|
||||
function printTail() {
|
||||
print <<<EOF
|
||||
</body>
|
||||
</html>
|
||||
EOF;
|
||||
}
|
||||
|
||||
function printNonAndroid($agent) {
|
||||
$subject = "Android device not identified";
|
||||
|
||||
$body = htmlentities("My browser is running on an android device but"
|
||||
. " says its user agent is: \"$agent\". Please fix your script to recognize"
|
||||
. " this as an Android browser.");
|
||||
print <<<EOF
|
||||
<div class="center">
|
||||
<p>This page is meant to be viewed on an Android device.</p>
|
||||
<hr>
|
||||
<p>(If you <em>are</em> viewing this on an Android device, you've
|
||||
found a bug! Please <a href="mailto:
|
||||
xwords@eehouse.org?subject=$subject&body=$body">email me</a> (and be
|
||||
sure to leave the user agent string in the email body.)
|
||||
</p>
|
||||
</div>
|
||||
|
||||
EOF;
|
||||
}
|
||||
|
||||
function printAndroid() {
|
||||
print <<<EOF
|
||||
<div>
|
||||
<p>You'll have come here after clicking a link in an email or
|
||||
text inviting you to a Crosswords game. But you should not be seeing
|
||||
this page.</p>
|
||||
|
||||
<p>If you got this page on your device, it means either
|
||||
<ul>
|
||||
<li>The copy of Crosswords you have is NOT beta 56 or newer (dating from about Dec. 1, 2012).</li>
|
||||
<li> OR </li>
|
||||
<li> that your copy of Crosswords is new enough <em>BUT</em> that
|
||||
when you clicked on the link and were asked to choose between a
|
||||
browser and Crosswords you chose the browser.</li>
|
||||
</ul></p>
|
||||
|
||||
<p>In the first case, install the latest Crosswords,
|
||||
either <a href="market://search?q=pname:org.eehouse.android.xw4">via
|
||||
the Google Play store</a> or
|
||||
(sideloading) <a href="https://sourceforge.net/projects/xwords/files/xwords_Android/4.4%20beta%2056/XWords4-release_android_beta_56.apk/download">via
|
||||
Sourceforge.net</a>. After the install is finished go back to the
|
||||
invite email (or text) and tap the link again.</p>
|
||||
|
||||
<p>In the second case, hit your browser's back button, click the
|
||||
link in your invite email (or text) again, and this time let
|
||||
Crosswords handle it.</p>
|
||||
|
||||
<p>Have fun. And as always, <a href="mailto:xwords@eehouse.org">let
|
||||
me know</a> if you have problems or suggestions.</p>
|
||||
</div>
|
||||
<div class="center">
|
||||
<img class="center" src="../icon48x48.png"/>
|
||||
</div>
|
||||
EOF;
|
||||
}
|
||||
|
||||
|
||||
/**********************************************************************
|
||||
* Main()
|
||||
**********************************************************************/
|
||||
$agent = $_SERVER['HTTP_USER_AGENT'];
|
||||
$onAndroid = false;
|
||||
for ( $ii = 0; $ii < count($g_androidStrings) && !$onAndroid; ++$ii ) {
|
||||
$needle = $g_androidStrings[$ii];
|
||||
$onAndroid = false !== stripos( $agent, $needle );
|
||||
}
|
||||
$onFire = false !== stripos( $agent, 'silk' );
|
||||
|
||||
printHead();
|
||||
if ( /*true || */ $onFire || $onAndroid ) {
|
||||
printAndroid();
|
||||
} else {
|
||||
printNonAndroid($agent);
|
||||
}
|
||||
printTail();
|
||||
|
||||
|
||||
?>
|
|
@ -1,2 +1,3 @@
|
|||
body { font-size: 2em; }
|
||||
table { font-size: 2em; }
|
||||
body { font-size: 1.5em; }
|
||||
table { font-size: 1.5em; }
|
||||
.center { text-align: center; }
|
||||
|
|
|
@ -45,7 +45,7 @@ CPPFLAGS += -DSPAWN_SELF -g -Wall \
|
|||
-I $(shell pg_config --includedir) \
|
||||
-DSVN_REV=\"$(shell cat $(GITINFO) 2>/dev/null || echo -n $(HASH) )\"
|
||||
# CPPFLAGS += -DDO_HTTP
|
||||
# CPPFLAGS += -DHAVE_SENDTIME
|
||||
# CPPFLAGS += -DHAVE_STIME
|
||||
|
||||
# turn on semaphore debugging
|
||||
# CPPFLAGS += -DDEBUG_LOCKS
|
||||
|
|
|
@ -581,8 +581,8 @@ DBMgr::PendingMsgCount( const char* connName, int hid )
|
|||
int count = 0;
|
||||
const char* fmt = "SELECT COUNT(*) FROM " MSGS_TABLE
|
||||
" WHERE connName = '%s' AND hid = %d "
|
||||
#ifdef HAVE_SENDTIME
|
||||
"AND sendtime IS NULL"
|
||||
#ifdef HAVE_STIME
|
||||
"AND stime IS NULL"
|
||||
#endif
|
||||
;
|
||||
string query;
|
||||
|
@ -692,8 +692,8 @@ DBMgr::CountStoredMessages( const char* const connName, int hid )
|
|||
{
|
||||
const char* fmt = "SELECT count(*) FROM " MSGS_TABLE
|
||||
" WHERE connname = '%s' "
|
||||
#ifdef HAVE_SENDTIME
|
||||
"AND sendtime IS NULL"
|
||||
#ifdef HAVE_STIME
|
||||
"AND stime IS NULL"
|
||||
#endif
|
||||
;
|
||||
|
||||
|
@ -748,8 +748,8 @@ DBMgr::GetNthStoredMessage( const char* const connName, int hid,
|
|||
{
|
||||
const char* fmt = "SELECT id, msg, msglen FROM " MSGS_TABLE
|
||||
" WHERE connName = '%s' AND hid = %d "
|
||||
#ifdef HAVE_SENDTIME
|
||||
"AND sendtime IS NULL "
|
||||
#ifdef HAVE_STIME
|
||||
"AND stime IS NULL "
|
||||
#endif
|
||||
"ORDER BY id LIMIT 1 OFFSET %d";
|
||||
string query;
|
||||
|
@ -807,8 +807,8 @@ DBMgr::RemoveStoredMessages( const int* msgIDs, int nMsgIDs )
|
|||
}
|
||||
|
||||
const char* fmt =
|
||||
#ifdef HAVE_SENDTIME
|
||||
"UPDATE " MSGS_TABLE " SET sendtime='now' "
|
||||
#ifdef HAVE_STIME
|
||||
"UPDATE " MSGS_TABLE " SET stime='now' "
|
||||
#else
|
||||
"DELETE FROM " MSGS_TABLE
|
||||
#endif
|
||||
|
|
|
@ -50,10 +50,12 @@ def init():
|
|||
|
||||
return con
|
||||
|
||||
# WHERE stime IS NULL
|
||||
|
||||
def getPendingMsgs( con, typ ):
|
||||
cur = con.cursor()
|
||||
query = """SELECT id, devid FROM msgs WHERE
|
||||
devid IN (SELECT id FROM devices WHERE devtype=%d)
|
||||
query = """SELECT id, devid FROM msgs
|
||||
WHERE devid IN (SELECT id FROM devices WHERE devtype=%d)
|
||||
AND NOT connname IN (SELECT connname FROM games WHERE dead); """
|
||||
cur.execute(query % typ)
|
||||
result = cur.fetchall()
|
||||
|
@ -75,23 +77,28 @@ def notifyGCM( devids, typ ):
|
|||
# 'msg' : "I am your father, Luke.",
|
||||
}
|
||||
response = instance.json_request( registration_ids = devids,
|
||||
data = data )
|
||||
|
||||
# restricted_package_name = 'org.eehouse.android.xw4',
|
||||
data = data,
|
||||
# collapse_key = 'NewMove',
|
||||
)
|
||||
if 'errors' in response:
|
||||
for error, reg_ids in response.items():
|
||||
print error
|
||||
response = response['errors']
|
||||
if 'NotRegistered' in response:
|
||||
for id in response['NotRegistered']:
|
||||
print 'need to remove "', id, '" from db'
|
||||
else:
|
||||
print 'no errors',
|
||||
if g_debug: print ':', response
|
||||
else: print
|
||||
print "got some kind of error"
|
||||
else:
|
||||
if g_debug: print 'no errors:', response
|
||||
else:
|
||||
print "not sending to", len(devids), "devices because typ ==", typ
|
||||
|
||||
def shouldSend(val):
|
||||
pow = 1
|
||||
while pow < val:
|
||||
pow *= 2
|
||||
return pow == val
|
||||
return val == 1
|
||||
# pow = 1
|
||||
# while pow < val:
|
||||
# pow *= 3
|
||||
# return pow == val
|
||||
|
||||
# given a list of msgid, devid lists, figure out which messages should
|
||||
# be sent/resent now and mark them as sent. Backoff is based on
|
||||
|
@ -99,7 +106,6 @@ def shouldSend(val):
|
|||
# before, backoff applies.
|
||||
def targetsAfterBackoff( msgs ):
|
||||
global g_sent
|
||||
print 'sent:', g_sent
|
||||
targets = {}
|
||||
for row in msgs:
|
||||
msgid = row[0]
|
||||
|
@ -174,16 +180,16 @@ def main():
|
|||
if 0 < len(targets):
|
||||
if 0 < emptyCount: print ""
|
||||
emptyCount = 0
|
||||
print strftime("%Y-%m-%d %H:%M:%S", gmtime()),
|
||||
print strftime("%Y-%m-%d %H:%M:%S", time.localtime()),
|
||||
print "devices needing notification:", targets
|
||||
notifyGCM( asGCMIds( g_con, targets, typ ), typ )
|
||||
pruneSent( devids )
|
||||
else:
|
||||
emptyCount += 1
|
||||
if not g_debug:
|
||||
if (0 == (emptyCount%5)) and not g_debug:
|
||||
sys.stdout.write('.')
|
||||
sys.stdout.flush()
|
||||
if 0 == (emptyCount % LINE_LEN): print ""
|
||||
if 0 == (emptyCount % (LINE_LEN*5)): print ""
|
||||
if 0 == loopInterval: break
|
||||
time.sleep( loopInterval )
|
||||
|
||||
|
|
|
@ -1,10 +1,13 @@
|
|||
#!/usr/bin/python
|
||||
|
||||
import sys, gcm, psycopg2
|
||||
import sys, gcm, psycopg2, json
|
||||
|
||||
# I'm not checking my key in...
|
||||
import mykey
|
||||
|
||||
def usage():
|
||||
print 'usage:', sys.argv[0], '[--to <name>] msg'
|
||||
sys.exit()
|
||||
|
||||
def msgViaGCM( devid, msg ):
|
||||
instance = gcm.GCM( mykey.myKey )
|
||||
|
@ -14,18 +17,30 @@ def msgViaGCM( devid, msg ):
|
|||
|
||||
response = instance.json_request( registration_ids = [devid],
|
||||
data = data )
|
||||
|
||||
if 'errors' in response:
|
||||
for error, reg_ids in response.items():
|
||||
print error
|
||||
response = response['errors']
|
||||
if 'NotRegistered' in response:
|
||||
ids = response['NotRegistered']
|
||||
for id in ids:
|
||||
print 'need to remove "', id, '" from db'
|
||||
else:
|
||||
print 'no errors'
|
||||
|
||||
|
||||
def main():
|
||||
to = None
|
||||
msg = sys.argv[1]
|
||||
print 'got "%s"' % msg
|
||||
msgViaGCM( mykey.myBlaze, msg )
|
||||
if msg == '--to':
|
||||
to = sys.argv[2]
|
||||
msg = sys.argv[3]
|
||||
elif 2 < len(sys.argv):
|
||||
usage()
|
||||
if not to in mykey.devids.keys():
|
||||
print 'Unknown --to param;', to, 'not in', ','.join(mykey.devids.keys())
|
||||
usage()
|
||||
if not to: usage()
|
||||
devid = mykey.devids[to]
|
||||
print 'sending: "%s" to' % msg, to
|
||||
msgViaGCM( devid, msg )
|
||||
|
||||
##############################################################################
|
||||
if __name__ == '__main__':
|
||||
|
|
|
@ -98,6 +98,7 @@ $cols = array( new Column("dead", "D", "capitalize", false ),
|
|||
new Column("clntVers", "CV", "identity", true ),
|
||||
new Column("nperdevice", "NP", "identity", true ),
|
||||
new Column("ack", "A", "identity", true ),
|
||||
new Column("devids", "DevIDs", "identity", true ),
|
||||
new Column("nsent", "Sent", "identity", false ),
|
||||
new Column("addrs", "Dev. addr", "ip_to_host", true ),
|
||||
new Column("ctime", "Created", "print_date", false ),
|
||||
|
|
|
@ -68,6 +68,7 @@ id SERIAL
|
|||
,connName VARCHAR(64)
|
||||
,hid INTEGER
|
||||
,ctime TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
,stime TIMESTAMP DEFAULT NULL
|
||||
,devid INTEGER
|
||||
,msg BYTEA
|
||||
,msglen INTEGER
|
||||
|
@ -81,6 +82,7 @@ id INTEGER UNIQUE PRIMARY KEY
|
|||
,devType INTEGER
|
||||
,devid TEXT
|
||||
,ctime TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
,unreg BOOLEAN DEFAULT FALSE
|
||||
);
|
||||
EOF
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue