track, and offer to display, comms-sent invites like others

I'm only keeping the most recent since they're sent every time a game
opens, app launches, etc.
This commit is contained in:
Eric House 2022-12-10 22:15:33 -08:00
parent b8a9e1715a
commit 53e1a68d6d
13 changed files with 204 additions and 85 deletions

View file

@ -1533,9 +1533,8 @@ public class BoardDelegate extends DelegateBase
}
@Override
public void onInfoClicked()
public void onInfoClicked( SentInvitesInfo sentInfo )
{
SentInvitesInfo sentInfo = DBUtils.getInvitesFor( m_activity, m_rowid );
String msg = sentInfo.getAsText( m_activity );
makeOkOnlyBuilder( msg )
.setTitle( R.string.title_invite_history )
@ -2543,7 +2542,7 @@ public class BoardDelegate extends DelegateBase
private InvitesNeededAlert.Wrapper getINAWrapper()
{
if ( null == mINAWrapper ) {
mINAWrapper = new InvitesNeededAlert.Wrapper( this );
mINAWrapper = new InvitesNeededAlert.Wrapper( this, m_jniGamePtr );
showOrHide( mINAWrapper );
}
return mINAWrapper;
@ -3055,6 +3054,9 @@ public class BoardDelegate extends DelegateBase
// recordInviteSent( InviteMeans.SMS_DATA, addr.sms_phone );
break;
case COMMS_CONN_NFC: // don't assert about this one
break;
default:
Log.d( TAG, "not inviting using addr type %s", typ );
Assert.failDbg();
@ -3099,7 +3101,7 @@ public class BoardDelegate extends DelegateBase
invitesSent = nSent >= m_mySIS.nMissing;
}
DBUtils.recordInviteSent( m_activity, m_rowid, means, dev );
DBUtils.recordInviteSent( m_activity, m_rowid, means, dev, false );
if ( !invitesSent ) {
Log.d( TAG, "recordInviteSent(): redoing invite alert" );

View file

@ -60,7 +60,7 @@ public class CommsTransport implements TransportProcs {
public boolean transportSendInvt( CommsAddrRec addr, NetLaunchInfo nli,
int timestamp )
{
return MultiMsgSink.sendInvite( m_context, addr, nli, timestamp );
return MultiMsgSink.sendInvite( m_context, m_rowid, addr, nli, timestamp );
}
@Override

View file

@ -450,12 +450,36 @@ public class DBUtils {
return result;
}
public static class SentInvite implements Serializable {
InviteMeans mMeans;
String mTarget;
Date mTimestamp;
public SentInvite( InviteMeans means, String target, Date ts )
{
mMeans = means;
mTarget = target;
mTimestamp = ts;
}
@Override
public boolean equals(Object otherObj)
{
boolean result = false;
if ( otherObj instanceof SentInvite ) {
SentInvite other = (SentInvite)otherObj;
result = mMeans == other.mMeans
&& mTarget.equals(other.mTarget)
&& mTimestamp.equals(other.mTimestamp);
}
return result;
}
}
public static class SentInvitesInfo
implements Serializable /* Serializable b/c passed as param to alerts */ {
public long m_rowid;
private ArrayList<InviteMeans> m_means;
private ArrayList<String> m_targets;
private ArrayList<Date> m_timestamps;
private ArrayList<SentInvite> mSents;
private int m_cachedCount = 0;
private boolean m_remotesRobots = false;
@ -465,11 +489,13 @@ public class DBUtils {
boolean result = null != other && other instanceof SentInvitesInfo;
if ( result ) {
SentInvitesInfo it = (SentInvitesInfo)other;
result = it.m_rowid == m_rowid
&& it.m_means.equals(m_means)
&& it.m_targets.equals(m_targets)
&& it.m_timestamps.equals(m_timestamps)
&& it.m_cachedCount == m_cachedCount;
if ( m_rowid == it.m_rowid
&& it.mSents.size() == mSents.size()
&& it.m_cachedCount == m_cachedCount ) {
for ( int ii = 0; result && ii < mSents.size(); ++ii ) {
result = it.mSents.get(ii).equals(mSents.get(ii));
}
}
}
// Log.d( TAG, "equals() => %b", result );
return result;
@ -478,30 +504,22 @@ public class DBUtils {
private SentInvitesInfo( long rowID )
{
m_rowid = rowID;
m_means = new ArrayList<>();
m_targets = new ArrayList<>();
m_timestamps = new ArrayList<>();
mSents = new ArrayList<>();
}
private void addEntry( InviteMeans means, String target, Date ts )
{
m_means.add( means );
m_targets.add( target );
m_timestamps.add( ts );
mSents.add( new SentInvite( means, target, ts ) );
m_cachedCount = -1;
}
public InviteMeans getLastMeans()
{
return 0 < m_means.size() ? m_means.get(0) : null;
}
public String getLastDev( InviteMeans means )
{
String result = null;
for ( int ii = 0; null == result && ii < m_means.size(); ++ii ) {
if ( means == m_means.get( ii ) ) {
result = m_targets.get( ii );
for ( SentInvite si : mSents ) {
if ( means == si.mMeans ) {
result = si.mTarget;
break;
}
}
return result;
@ -514,19 +532,20 @@ public class DBUtils {
public int getMinPlayerCount()
{
if ( -1 == m_cachedCount ) {
int count = m_timestamps.size();
int count = mSents.size();
Map<InviteMeans, Set<String>> hashes
= new HashMap<InviteMeans, Set<String>>();
int fakeCount = 0; // make all null-targets count for one
for ( int ii = 0; ii < count; ++ii ) {
InviteMeans means = m_means.get(ii);
SentInvite si = mSents.get(ii);
InviteMeans means = si.mMeans;
Set<String> devs;
if ( ! hashes.containsKey( means ) ) {
devs = new HashSet<>();
hashes.put( means, devs );
}
devs = hashes.get( means );
String target = m_targets.get( ii );
String target = si.mTarget;
if ( null == target ) {
target = String.format( "%d", ++fakeCount );
}
@ -548,15 +567,15 @@ public class DBUtils {
public String getAsText( Context context )
{
String result;
int count = m_timestamps.size();
int count = mSents.size();
if ( 0 == count ) {
result = LocUtils.getString( context, R.string.no_invites );
} else {
String[] strs = new String[count];
for ( int ii = 0; ii < count; ++ii ) {
InviteMeans means = m_means.get(ii);
String target = m_targets.get(ii);
String timestamp = m_timestamps.get(ii).toString();
List<String> strs = new ArrayList<>();
for ( SentInvite si: mSents ) {
InviteMeans means = si.mMeans;
String target = si.mTarget;
String timestamp = si.mTimestamp.toString();
String msg = null;
switch ( means ) {
@ -590,8 +609,9 @@ public class DBUtils {
means.toString(), timestamp );
}
strs[ii] = msg;
strs.add( msg );
}
result = TextUtils.join( "\n\n", strs );
}
return result;
@ -599,18 +619,20 @@ public class DBUtils {
public String getKPName( Context context )
{
String result = null;
for ( int ii = 0; ii < m_means.size(); ++ii ) {
InviteMeans means = m_means.get(ii);
String mqttID = null;
for ( SentInvite si : mSents ) {
InviteMeans means = si.mMeans;
if ( means == InviteMeans.MQTT ) {
String player = XwJNI.kplr_nameForMqttDev( m_targets.get(ii) );
if ( null != player ) {
result = player;
break; // ordered newest-first, so we're done
}
mqttID = si.mTarget;
break;
}
}
String result = null;
if ( null != mqttID ) {
result = XwJNI.kplr_nameForMqttDev( mqttID );
}
Log.d( TAG, "getKPName() => %s", result );
return result;
}
@ -655,7 +677,8 @@ public class DBUtils {
}
public static void recordInviteSent( Context context, long rowid,
InviteMeans means, String target )
InviteMeans means, String target,
boolean dropDupes )
{
if ( BuildConfig.NON_RELEASE ) {
switch ( means ) {
@ -665,15 +688,31 @@ public class DBUtils {
case WIFIDIRECT:
case SMS_USER:
case QRCODE:
break;
case MQTT:
case SMS_DATA:
case BLUETOOTH:
case MQTT:
break;
case RELAY:
default:
Assert.failDbg();
}
}
String dropTest = null;
if ( dropDupes ) {
dropTest = String.format( "%s = %d AND %s = %d",
DBHelper.ROW, rowid,
DBHelper.MEANS, means.ordinal() );
if ( null != target ) {
dropTest += String.format( " AND %s = '%s'",
DBHelper.TARGET, target );
} else {
// If I'm seeing this, need to check above if a "target is
// null" test is needed to avoid nuking unintentinally.
Assert.failDbg();
}
}
ContentValues values = new ContentValues();
values.put( DBHelper.ROW, rowid );
values.put( DBHelper.MEANS, means.ordinal() );
@ -683,6 +722,9 @@ public class DBUtils {
initDB( context );
synchronized( s_dbHelper ) {
if ( null != dropTest ) {
delete( TABLE_NAMES.INVITES, dropTest );
}
insert( TABLE_NAMES.INVITES, values );
}
}

View file

@ -32,6 +32,7 @@ import org.eehouse.android.xw4.Perms23.Perm;
import org.eehouse.android.xw4.jni.CommsAddrRec.CommsConnType;
import org.eehouse.android.xw4.jni.CommsAddrRec;
import org.eehouse.android.xw4.jni.XwJNI;
import org.eehouse.android.xw4.jni.XwJNI.GamePtr;
import org.eehouse.android.xw4.loc.LocUtils;
class InvitesNeededAlert {
@ -45,8 +46,13 @@ class InvitesNeededAlert {
private Callbacks mCallbacks;
private InvitesNeededAlert mSelf;
private CommsAddrRec mHostAddr;
private GamePtr mGamePtr;
Wrapper( Callbacks callbacks ) { mCallbacks = callbacks; }
Wrapper( Callbacks callbacks, GamePtr gamePtr )
{
mCallbacks = callbacks;
mGamePtr = gamePtr;
}
void showOrHide( CommsAddrRec hostAddr, int nPlayersMissing, int nInvited,
boolean isRematch )
@ -75,7 +81,7 @@ class InvitesNeededAlert {
AlertDialog make( DBAlert alert, Object[] params )
{
DbgUtils.assertOnUIThread();
return mSelf.makeImpl( mCallbacks, alert, mHostAddr, params );
return mSelf.makeImpl( mCallbacks, alert, mHostAddr, mGamePtr, params );
}
void dismiss()
@ -120,7 +126,7 @@ class InvitesNeededAlert {
long getRowID();
void onCloseClicked();
void onInviteClicked();
void onInfoClicked();
void onInfoClicked( SentInvitesInfo sentInfo );
}
private boolean close()
@ -146,7 +152,8 @@ class InvitesNeededAlert {
}
private AlertDialog makeImpl( final Callbacks callbacks, DBAlert alert,
CommsAddrRec hostAddr, Object[] params )
CommsAddrRec hostAddr, GamePtr gamePtr,
Object[] params )
{
State state = (State)params[0];
AlertDialog.Builder ab = mDelegate.makeAlertBuilder();
@ -154,7 +161,7 @@ class InvitesNeededAlert {
int[] closeLoc = { AlertDialog.BUTTON_NEGATIVE };
if ( state.mIsServer ) {
makeImplHost( ab, callbacks, alert, state, closeLoc );
makeImplHost( ab, callbacks, alert, state, gamePtr, closeLoc );
} else {
makeImplGuest( ab, state, hostAddr );
}
@ -217,13 +224,15 @@ class InvitesNeededAlert {
}
private void makeImplHost( AlertDialog.Builder ab, final Callbacks callbacks,
DBAlert alert, State state, int[] closeLoc )
DBAlert alert, State state, GamePtr gamePtr,
int[] closeLoc )
{
Context context = mDelegate.getActivity();
final int nPlayersMissing = state.mNPlayersMissing;
long rowid = callbacks.getRowID();
SentInvitesInfo sentInfo = DBUtils.getInvitesFor( context, rowid );
int nSent = state.mNInvited + sentInfo.getMinPlayerCount();
boolean invitesNeeded = nPlayersMissing > nSent;
@ -271,5 +280,15 @@ class InvitesNeededAlert {
alert.setNoDismissListenerNeg( ab, inviteButtonTxt, onInvite );
closeLoc[0] = DialogInterface.BUTTON_POSITIVE;
}
if ( BuildConfig.NON_RELEASE && 0 < nSent ) {
alert.setNoDismissListenerNeut( ab, R.string.button_invite_history,
new OnClickListener() {
@Override
public void onClick( DialogInterface dlg, int item ) {
callbacks.onInfoClicked( sentInfo );
}
} );
}
}
}

View file

@ -22,9 +22,9 @@ package org.eehouse.android.xw4;
import android.content.Context;
import org.eehouse.android.xw4.jni.CommsAddrRec;
import org.eehouse.android.xw4.DlgDelegate.DlgClickNotify.InviteMeans;
import org.eehouse.android.xw4.jni.CommsAddrRec.CommsConnType;
import org.eehouse.android.xw4.jni.CommsAddrRec;
import org.eehouse.android.xw4.jni.TransportProcs;
import java.util.HashSet;
@ -99,7 +99,7 @@ public class MultiMsgSink implements TransportProcs {
public boolean transportSendInvt( CommsAddrRec addr, NetLaunchInfo nli,
int timestamp )
{
return sendInvite( m_context, addr, nli, timestamp );
return sendInvite( m_context, m_rowid, addr, nli, timestamp );
}
@Override
@ -148,33 +148,49 @@ public class MultiMsgSink implements TransportProcs {
Log.d( TAG, "countChanged(new=%d); dropping", newCount );
}
public static boolean sendInvite( Context context, CommsAddrRec addr,
NetLaunchInfo nli, int timestamp )
public static boolean sendInvite( Context context, long rowid,
CommsAddrRec addr, NetLaunchInfo nli,
int timestamp )
{
Log.d( TAG, "sendInvite(%s, %s)", addr, nli );
boolean success = false;
for ( CommsConnType typ : addr.conTypes ) {
InviteMeans means = null;
String target = null;
switch ( typ ) {
case COMMS_CONN_MQTT:
target = addr.mqtt_devID;
means = InviteMeans.MQTT;
MQTTUtils.sendInvite( context, addr.mqtt_devID, nli );
success = true;
break;
case COMMS_CONN_SMS:
if ( XWPrefs.getNBSEnabled( context ) ) {
NBSProto.inviteRemote( context, addr.sms_phone, nli );
target = addr.sms_phone;
means = InviteMeans.SMS_DATA;
success = true;
}
break;
case COMMS_CONN_BT:
BTUtils.sendInvite( context, addr.bt_btAddr, nli );
target = addr.bt_btAddr;
means = InviteMeans.BLUETOOTH;
success = true;
break;
case COMMS_CONN_NFC: // nothing to do
break;
default:
Log.d( TAG, "sendInvite(); not handling %s", typ );
// Assert.failDbg();
Assert.failDbg();
break;
}
if ( null != means ) {
DBUtils.recordInviteSent( context, rowid, means, target, true );
}
}
Log.d( TAG, "sendInvite(%s) => %b", addr, success );
return success;
}

View file

@ -964,7 +964,7 @@
launched to send your invitation. -->
<string name="newgame_invite">Invite now</string>
<string name="newgame_reinvite">Invite again</string>
<!-- Button offering to invite Known Player to a new game -->
<!-- Title of alert showing previous invitation sent -->
<string name="title_invite_history">Invitations Sent</string>
<string name="newgame_drop_mqtt">Drop Internet</string>
<!-- EXPLAIN ME -->

View file

@ -9,4 +9,7 @@
message but not heard back -->
<string name="missing_host_fmt">FYI, the host is a Known Player called “%1$s”.</string>
<!-- Button offering to show info about invites already sent -->
<string name="button_invite_history">History</string>
</resources>

View file

@ -601,6 +601,20 @@ addrTypesToJ( JNIEnv* env, const CommsAddrRec* addr )
return result;
}
jobjectArray
makeAddrArray( JNIEnv* env, XP_U16 count, const CommsAddrRec* addrs )
{
jclass clas = (*env)->FindClass( env, PKG_PATH("jni/CommsAddrRec") );
jobjectArray result = (*env)->NewObjectArray( env, count, clas, NULL );
for ( int ii = 0; ii < count; ++ii ) {
jobject jaddr = makeJAddr( env, &addrs[ii] );
(*env)->SetObjectArrayElement( env, result, ii, jaddr );
deleteLocalRef( env, jaddr );
}
deleteLocalRef( env, clas );
return result;
}
/* Writes a java version of CommsAddrRec into a C one */
void
getJAddrRec( JNIEnv* env, CommsAddrRec* addr, jobject jaddr )

View file

@ -101,6 +101,8 @@ void getJAddrRec( JNIEnv* env, CommsAddrRec* addr, jobject jaddr );
void setTypeSetFieldIn( JNIEnv* env, const CommsAddrRec* addr, jobject jTarget,
const char* fldName );
jobject addrTypesToJ( JNIEnv* env, const CommsAddrRec* addr );
jobjectArray makeAddrArray( JNIEnv* env, XP_U16 count,
const CommsAddrRec* addrs );
jint jenumFieldToInt( JNIEnv* env, jobject jobj, const char* field,
const char* fieldSig );
void intToJenumField( JNIEnv* env, jobject jobj, int val, const char* field,

View file

@ -2277,16 +2277,7 @@ Java_org_eehouse_android_xw4_jni_XwJNI_comms_1getAddrs
CommsAddrRec addrs[MAX_NUM_PLAYERS];
XP_U16 count = VSIZE(addrs);
comms_getAddrs( state->game.comms, env, addrs, &count );
jclass clas = (*env)->FindClass( env, PKG_PATH("jni/CommsAddrRec") );
result = (*env)->NewObjectArray( env, count, clas, NULL );
for ( int ii = 0; ii < count; ++ii ) {
jobject jaddr = makeJAddr( env, &addrs[ii] );
(*env)->SetObjectArrayElement( env, result, ii, jaddr );
deleteLocalRef( env, jaddr );
}
deleteLocalRef( env, clas );
result = makeAddrArray( env, count, addrs );
}
XWJNI_END();
return result;
@ -2701,6 +2692,26 @@ Java_org_eehouse_android_xw4_jni_XwJNI_comms_1invite
}
XWJNI_END();
}
# if 0
JNIEXPORT jobjectArray JNICALL
Java_org_eehouse_android_xw4_jni_XwJNI_comms_1getInvites
( JNIEnv* env, jclass C, GamePtrType gamePtr )
{
jobjectArray result = NULL;
XWJNI_START_GLOBALS(gamePtr);
CommsCtxt* comms = state->game.comms;
XP_ASSERT( NULL != comms );
if ( NULL != comms ) {
CommsAddrRec inviteRecs[4];
XP_U16 nInvites = VSIZE(inviteRecs);
comms_getInvited( comms, env, &nInvites, inviteRecs );
result = makeAddrArray( env, nInvites, inviteRecs );
}
XWJNI_END();
return result;
}
# endif
#endif
JNIEXPORT void JNICALL

View file

@ -214,9 +214,9 @@ static void augmentChannelAddr( CommsCtxt* comms, AddressRecord* rec,
const CommsAddrRec* addr, XWHostID hostID );
static XP_Bool augmentAddrIntrnl( CommsCtxt* comms, CommsAddrRec* dest,
const CommsAddrRec* src, XP_Bool isNewer );
static XP_Bool channelToAddress( CommsCtxt* comms, XWEnv xwe, XP_PlayerAddr channelNo,
const CommsAddrRec** addr );
static AddressRecord* getRecordFor( CommsCtxt* comms, XWEnv xwe,
static XP_Bool channelToAddress( const CommsCtxt* comms, XWEnv xwe,
XP_PlayerAddr channelNo, const CommsAddrRec** addr );
static AddressRecord* getRecordFor( const CommsCtxt* comms, XWEnv xwe,
const CommsAddrRec* addr, XP_PlayerAddr channelNo );
static void augmentSelfAddr( CommsCtxt* comms, XWEnv xwe, const CommsAddrRec* addr );
static XP_S16 sendMsg( CommsCtxt* comms, XWEnv xwe, MsgQueueElem* elem,
@ -1625,11 +1625,11 @@ comms_invite( CommsCtxt* comms, XWEnv xwe, const NetLaunchInfo* nli,
LOG_RETURN_VOID();
}
XP_U16
comms_getInvited( const CommsCtxt* comms )
void
comms_getInvited( const CommsCtxt* comms, /*XWEnv xwe, */
XP_U16* nInvites, CommsAddrRec* inviteRecs )
{
LOG_FUNC();
XP_U16 result = 0;
XP_U16 count = 0;
XP_U16 allBits = 0;
for ( const MsgQueueElem* elem = comms->msgQueueHead; !!elem;
@ -1640,14 +1640,23 @@ comms_getInvited( const CommsCtxt* comms )
XP_U16 thisBit = 1 << channelNo;
XP_ASSERT( 0 == (thisBit & allBits) ); /* should be no dupes */
if ( 0 == (thisBit & allBits) ) {
++result;
XP_ASSERT( !inviteRecs );
/* if ( !!inviteRecs && count < *nInvites ) { */
/* const CommsAddrRec* rec; */
/* if ( channelToAddress( comms, xwe, channelNo, &rec ) ) { */
/* inviteRecs[count] = *rec; */
/* } else { */
/* XP_ASSERT(0); /\* what to do? Fail the whole thing? *\/ */
/* } */
/* } */
++count;
}
allBits |= thisBit;
}
}
LOG_RETURNF( "%d", result );
return result;
*nInvites = count;
// LOG_RETURNF( "%d", *nInvites );
}
#endif
@ -2525,7 +2534,7 @@ preProcess(
} /* preProcess */
static AddressRecord*
getRecordFor( CommsCtxt* comms, XWEnv xwe, const CommsAddrRec* addr,
getRecordFor( const CommsCtxt* comms, XWEnv xwe, const CommsAddrRec* addr,
const XP_PlayerAddr channelNo )
{
AddressRecord* rec;
@ -3634,7 +3643,7 @@ augmentAddr( CommsAddrRec* addr, const CommsAddrRec* newer, XP_Bool isNewer )
}
static XP_Bool
channelToAddress( CommsCtxt* comms, XWEnv xwe, XP_PlayerAddr channelNo,
channelToAddress( const CommsCtxt* comms, XWEnv xwe, XP_PlayerAddr channelNo,
const CommsAddrRec** addr )
{
AddressRecord* recs = getRecordFor( comms, xwe, NULL, channelNo );

View file

@ -212,7 +212,8 @@ void addrToStream( XWStreamCtxt* stream, const CommsAddrRec* addr );
#ifdef XWFEATURE_COMMS_INVITE
void comms_invite( CommsCtxt* comms, XWEnv xwe, const NetLaunchInfo* nli,
const CommsAddrRec* destAddr );
XP_U16 comms_getInvited( const CommsCtxt* comms );
void comms_getInvited( const CommsCtxt* comms, /*XWEnv xwe, */
XP_U16* nInvites, CommsAddrRec* inviteRecs );
#endif
XP_S16 comms_send( CommsCtxt* comms, XWEnv xwe, XWStreamCtxt* stream );
XP_S16 comms_resendAll( CommsCtxt* comms, XWEnv xwe, CommsConnType filter,

View file

@ -518,7 +518,7 @@ informMissing( const ServerCtxt* server, XWEnv xwe )
nPending = server->nv.pendingRegistrations;
nDevs = server->nv.nDevices - 1;
if ( 0 < nPending ) {
nInvited = comms_getInvited( comms );
comms_getInvited( comms, &nInvited, NULL );
if ( nPending < nInvited ) {
nInvited = nPending;
}