Looks like certain launches that went through DispatchNotify could
create a second "instance" of the app. Giving that Activity a launchMode
of singleTask fixes this (singleTop does not.)
Launching a game in response to a wordlist it needing being downloaded
went through a different Intent-creation path from the rest, and
resulted in a second instance of the app showing in the launcher. This
shoudl fix that. I don't *think* it's the only way I'm getting second
instances or stacked GamesLists, but it should get fixed.
I modified existing translated strings, adding the new clause. Not doing
that would cause the existing strings to be stripped because they'd no
longer have the matching set of format specifiers.
Fix a race condition introduced by making initing jnithread be
asynchronous: all the layout that was supposed to happen after listeners
were added instead happened before, so that they weren't there to be
installed as part of layout. So now, after adding them, get them hooked
up to the UI. (The complexity of this is all historical: at some point
the listeners were getting added BEFORE there were views to attach them
to, so they were cached and added later. Probably now they could simply
be installed as part of adding them. But I'm not doing that now.)
Was seeing frequent failure to open games as JNIThread.getRetained() was
unable to get a lock without waiting. Which it can't do on UI thread. So
added a method to GameLock that takes a callback to be called when the
lock's obtained, with the actual waiting done on a local thread. Then
fixed BoardDelegate a bit to not crash while waiting for the callback.
Once built for SDK 26 the old GCM support code crashes as it's calling
startService() from background. Duh! Will have to bring in Firebase's
replacement at some point. For now if I'm to release it has to be
without GCM.
more dev-only code, likely: if the game that's sending the invitation is
open when the invitation arrives, should still check if it's a dupe or
should be accepted.
Was creating a second game in some cases in response to duplicated
invitations. Add a test for forceChannel: if there's already a game with
the same gameID *and* forceChannel assume the invitation is a dupe and
drop it.
So I don't have to open a game to see e.g. how it's connecting, refactor
the network status stuff so it can be called from both board and
gameslist views. Then add new context menuitem that calls it.
Instead, keep them forever (for now), sorted by how long since they were
last seen. A Delete button's probably needed to prevent ex-partners from
sticking around too long. :-)
Adding a second permission a while back resulted in two Actions being
received and acted on. Needed instead to group the two and receive a
single Action IFF both were granted.
Put up an error message if too many tiles selected for trade
(a condition that couldn't exist when the pool was guaranteed to
have at least 7 in it.) (It's a hack: there's not even an enum
giving Spanish's code, and the lang_locale stuff in info.txt isn't
making it into the .xwd format.)
I've learned a bit about java since writing that class. Should be faster
now without the sleep/polling. Also making it illegal to use blocking
lock() call on UI thread. There may be some assertions to fix in the
next few days.
Was seeing race condition on slowest device where JNIThread init hadn't
finished before the event loop tried to use the gamePtr. So use
wait()/notifyAll() to fix.
And: Don't start the foreground service with its user-visible
notification, as that's too obtrusive and the ACL_CONNECTED stuff seems
to work well enough. Launching timers on a new install is mostly for my
own dev use, but won't hurt user experience either.
On O and beyond it's possible to tweak notification channels separately,
meaning users can hide the new BT-is-running notification and still get
game event notifications. So add a button that takes you to the right
Settings app page.
Separate processing of sockets from accepting them so that when an ACL
CONN notification is received and we open a socket (but don't yet have a
Service running because the ACL thing is most likely for some other app)
we can set it aside to be processed once we do have a service. Use the
same block-until-non-null thing as in RelayService to keep that thread
free of NPEs.
Rather than just dropping it and going back to a wait that will likely
last forever. I *think* wait() throwing that exception means the
thread's being killed, in which case the exception should be thrown up
to the containing loop that will then exit.
I thought I had to stop using a service instance before returning from
its onDestroy(), but that made the UI incredibly laggy AND appears not
to be necessary. At least in a bit of testing things still work.
Oreo's creating a new service instance for every single intent passed to
enqueueWork, meaning a brand new set of threads with a new queue, empty
set of messages to be retried, etc. was created every time, and all
attempts to optimize and retry were broken. So: make the threads a
static singleton that are given a Service instance from onCreate() and
told to drop it from onDestroy(). The threads proceed until they need an
instance, then block until one's available. Seems to work on Oreo and an
older Android as well.
Apparently one of the newer Android SDK levels adds the requirement to
have RECEIVE_SMS permission in order for a broadcast receiver to get
called. Meaning receipt didn't work even if SEND_SMS had been
granted. Since they're both in the same group (for now) the OS will
grant the second silently if the first has been granted, but it still
has to be requested. So request both at the same time. This still leaves
the problem that a user who's never tried to create an SMS game won't
have been asked for either permission and so won't receive SMS
invitations, but fixing that is for a later release.
Other code will take care of duplicates. This was meant to avoid a race
condition, for which 5 seconds is enough. Blocking forever complicates
testing.
Receiver is created and installed by a non-UI thread sometimes so can't
create the Handler there. onReceive() is called on an ok thread however
so create it there on demand. Fixes crash that showed when receiving
relay messages in background.
Fire up the receiver thread, and start the service, on receipt of this
ACTION (if they're not already running.) On start, the service takes
over the thread and begins dispatching messages. Works to launch the app
when it's not running and in most cases, though messages received before
the service launches are currently dropped, things seem to work.
reset timer on receiving meaningful data and on moving app to
background. If it fires and we're in background, kill the service.
(There's currently no way to restart it except bringing the app into the
foreground. Fixing that's coming.)
Moving toward a better BT invite experience: use BTService to scan for
ourselves on all paired devices, and only allow selecting from among
those on which we're running (and so likely to respond to an
invitation.)
It's a pain to have to change code to run in Genymotion, and to have
upgrade not testable using Genymotion. Consider verting this change
before next release.
Did a bunch of stuff to inherit from JobIntentService and use enqueue(),
but doesn't work yet. OS is unable to bind, with this error:
09-21 17:20:51.678 3050 3050 W JobServiceContext: Time-out while trying to bind 2edee28 #u0a277/1111 org.eehouse.android.xw4dbg/org.eehouse.android.xw4.BTService, dropping.
Notifications don't work on Oreo without this change, which includes a
new Support Library in order to get NotificationChannel and creates and
uses that as docs describe. Requires that MinSDK be raised from 8 to 14,
which may lock some users out. It *should* be possible not to do this in
the fdroid variant since their app store doesn't requires SDK 26, but
I'll look at that later.
For some reason my laptop wouldn't build without this change. No idea
what happened to the newer version I was using or if the change
works (beyond compiling). Should be easy to find the change later if
it's a problem.
Some devices, including my Moto, are apparently calling server_do() more
than most. When the game's supposed to be asking the user to pick tiles
that resulted in stacked TilePickAlerts. The stack of these
sending (taken together) too many picked tiles to the game made it
crash. So modify server to have only one pending tile-pick request going
at a time. Because the server can't know when the user dismisses the
alert in Android and so won't post again, respond to the dismissal by
closing the game. Reopening will put it in a state where the tile picker
can get called again.
Logic error meant it was never sent. Now always send on receipt of a
well-formed invitation, even if e.g. the recipient's missing a wordlist
and the game can't be started immediately.
Had inadventently changed how NetLaunchInfo was transmitted, and crashed
on receiving from older builds. Fix to not crash, and then fix to send
and recieve in the old format.
So now all jni code uses a single dutil context, but also a single
mempool and jniutil instance instead of new instances of the latter two
per game and dict-iteration.
A number of jni calls were "stateless", which meant they allocated their
own vtmgr and mpool instances each time invoked. Instead invoke them
with the global jni closure and add to it vtmgr (already has mpool) and
use these instead of allocating/freeing each time. To make sure no race
conditions are introduced (mpool, though debug-only, is probably not
thread-safe), guard these new uses with an in-use flag. If that fires
I'll need a mutex or something.
Somehow I got a wordlist into a location different from what was
recorded in the DB table and since the delete command matched on
location as well as name it was never deleted (which meant the checksum
was never updated and so upgrading never seemed to succeed.) Removing
the match on location fixed that problem, and since I don't see any harm
in cacheing only one version of a wordlist will simply leave it that
way.
Did a bunch of cleanup as well.
I'm seeing several IllegalStateException crashes due to e.g. having an
alert posted when app's in the background. Need to fix them, but the
debug build crashing isn't helpful. Log.e() instead.
The pesky thing is back. When app's in the background with an
unconnected game open displaying the "resend/wait" alert and the game
connects get IllegalStateException because the fragment stack's being
modified after onSaveInstanceState() (or, because the dialog fragment,
saved as an instance variable in BoardDelegate, dates from an earlier
state. Anyway, catching and dropping the exception and elsewhere failing
silently to rebuild the alert seems to fix the problem, but the right
fix is likely different. I suspect hanging onto that iVar is wrong, and
that the dialog should go away when onStop() is called and then be
rebuilt later from saved state. But for now, not crashing is good.
Remove a bunch of duplicated code, replacing with implementation in
XWService. Fixes duplicate invitation and game opening policies being
slightly different.
Try some funky layout shite to get, within a horizontal linear layout,
the first text field trucated if necessary so that the second (holding
the score) can be fully displayed. Tested on exactly one emulator so
far.
Try some funky layout shite to get, within a horizontal linear layout,
the first text field trucated if necessary so that the second (holding
the score) can be fully displayed. Tested on exactly one emulator so
far.
I think there's a bug in Weblate because I've seen this before: where
the English provides only an <other> the translation comes back with
only a <one>. That's wrong. Try adding a <one> in the English case to
see if that makes a difference.
I think there's a bug in Weblate because I've seen this before: where
the English provides only an <other> the translation comes back with
only a <one>. That's wrong. Try adding a <one> in the English case to
see if that makes a difference.
Otherwise it takes too long to scroll if you have hundreds of
games. To make this work had to move scroller to left side of games
list display as otherwise the scroller steals events from the expander
thingies.
Likely because of something in the jni world unset per-player dict name
is empty string rather than null, so test for that too. Fixes dicts
popup in newly-created game have an empty first line " (in use)".
I like this better than the previous fix: rather than share a
thread->env map with the game world allocate a new one for each
iterator. This could cause problem if the iterator is used on threads
that don't currently call map_thread(), or if there are callbacks that
need to look up the env that I'm not aware of. Needs testing...
Stumbled on a NPE opening up the wordlist browser configuring the first
game on a new install. So now test for null there and init early if
necessary. Seems to work, and won't do anything in places were not
needed.
Remake the min and max spinners every time either value changes so they
can't be used to set nonsensical values. (Which leads to immediate
crashes.) I'm sure this wasn't always a problem, but...
As reported to google, dict iterator destruction was crashing due to a
race condition if it happened after a game using the same dict had been
closed since it needed a mapped env that the game closure would
remove. Fixed in two ways, one by adding the mapping prior to the code
that uses it (a common pattern: add happens many times, whenver it might
be needed, but remove only once), and second by passing env into the
code that was crashing.
The mapping stuff remains inherently racy and I'm not sure now how to
fix that. It depends on there being a place to unmap after which it's
guaranteed the mapped value won't be needed again. When two
objects (game and dict_iter in this case) map the same env/thread combo
there's a race.
Looks like the assertion was left in when adding support for dual-pane
mode, as all other onPosButton() implementations called super rather
than assert. Which this one does now too.
Getting ANRs because (I think) the main thread's waiting for the write
thread to die and now the write thread's doing a ton of work
sometimes. So move the threads into a standalone object that can be
allowed to die on its own time without anybody waiting.
I *think* the reason I'm occasionally seeing toasts about not finding a
move is that when the engine's interrupted by there being a UI event in
the queue that error is posted. Instead try posting only when at the end
of the search nothing's been found.
Having reconfigured to use non-existent relay port as a test of falling
back to the web apis, tweak stuff: send the packets that have been
accumulated when an EOQ is found (rather than dropping all of them
immediately) before exiting the write thread; and start the threads up
when posting a packet in case they aren't (they may not be when the post
happens via timer firing.)
Seemed to be causing ANRs. Integrate instead into outgoing message queue
by using poll(timeout) then checking for unack'd packets every time
through the loop (but not more than once/3 seconds or so.)
Presence of timestamp instead of a boolean determines whether packet
should next via Web. Timestamps might also allow to process a larger
number of unacked packets in a single timer fire....
Track ack'd and unack'd packets. When there are ten more of the latter,
skip the UDP-send step. This is probably not the algorithm I'll settle
on (an explicit PING to the relay over UDP might be simpler), but it's
simple and easy.
Send each packet via UDP if that's thought to be working (always is,
now) and start a 10-second timer. If it hasn't been ack'd by then,
resend via Web API. Tested by configuring to use a UDP socket that the
relay isn't listening on. Only problem is that the backoff timers are
broken: never stops sending every few seconds.
The plan's to use the native relay protocol first, then to fall back to
the slower but more reliable (esp. on paranoid/block-everything wifi
networks) webAPI. This is the name change without behavior
change (except that the native kill() to report deleted games is gone.)
When grouping to allow multiple packets per outbound API call I forgot
that some are there to mark the end-of-queue: can't be sent! Trying
caused a NPE. Now if any EOQ is found in the queue that batch is dropped
and the thread's exited.
Making the right_side elem match its parent height prevents the
lower-right region of game list items from falling through and
triggering a toggle-selection event.
Making the right_side elem match its parent height prevents the
lower-right region of game list items from falling through and
triggering a toggle-selection event.
As expected, moves are no longer received instantly because the UDP
socket isn't available for the relay to write too once the URL
handler (relay.py) finishes.
Ideally the comms module wouldn't go through its connecting routine in
order to join a game. To that end I added a join() method to relay.py
and code to call it. Joins happen (pairing games, starting new ones,
etc.), but after that communication doesn't. First part of fixing that
would be to make cookieID persistent and transmit it back with the rest
of what join sends (since it's used by all the messages currently sent
in a connected state), but I suspect there's more to be done, and even
that requires a fair number of changes on the relay side. So all that's
wrapped in #ifdef RELAY_VIA_HTTP (and turned off.)
Was coming in as a string when called via curl. This may be a problem
for other ints if I go with lots of params instead of a json, which is
looking less likely.
join's how devices will create or join existing games. It more
compilicated than I'd like but seems to work except that once a slot's
assigned it's unavailable to anybody else even if the other fails ever
to respond (i.e. needs the ACK function of the c++ relay.)
ACK doesn't need to wait 2 seconds for a reply, and when it does so the
next send waits too. Eventually we'll want to combine messages already
in the queue into a single send. For now, this makes things better.
So far uses curl and json-c to send b64-encoded data to new script
which is able to echo the data. Next that script will need to open a
UDP socket to the relay and return results that appear before timeout.
Working around there being a border around the game-type image area.
With this change long-tapping works only on the right 2/3 of the
region. There might be a fix, but it's still better than there being
a hole (the border) in the thing where behavior's different.
Duh. The .java file was removed, but the declaration that all apps can
handle an intent (that requires WakeLock they no longer all have) was
not. Should fix crashes I'm seeing.
Board and Games List were using same menuid which meant that even when
chosen from Board's menu it would up getting handled by GamesList (in
dual-pane mode.)
Somehow the d variant was crashing without the WAKELOCK permission, the
OS having invokes something GCM-related. This should ensure nothing
GCM-related can ever happen.
This seems to fix that app, when built by me where GCM_SENDER_ID's set
in the environment, being a battery hog. Apparently google's code
doesn't handle being passed the wrong senderID very well.
I'd added inputType="text" everywhere I added maxLines="1" but it turns
out that breaks touches being handled at least on some devices. And it
makes no sense to have an inputType for something user can't put into.
Several EditText fields are configured so monkey breaks things, e.g. by
entering too much text in the default player name config. Fix. This
probably won't impact users but it lets the monkey tests move on to
other things that might.
Can't repro on Nexus emulator running 4.4 nor on Samsung running 4.4.4,
but the reporter says this fixes it. And from reading it appears
expecting older devices to load Material themes without an AppCompat
library is wrong.
For the ORDER BY clause that governs how games are displayed within a
group, use a static string built from a list of clauses that are then
easy to move up and down. Add clause that moves games with unread chat
to the top. Another commit will modify the display so it's clear why
it's there.
Bring back the test for being null, and add an additional one via a new
boolean iVar that we haven't tried posting it already. On some devices
there's enough of a lag that the is-null test passes several times
before the first makeDialog() call, leading to a cascade of attempts to
create that hangs the UI. The test's needed, but only once should be
start the process.
When the back stack is restored commit() is not called, so the fragment
needs to save it. Without this 0 is passed to popBackStack() and
everything's dismissed, not just the one fragment.
Rather than just pop whatever's on top of the back stack, which might be
the alert whose listener called finish(), use saved commit() ids to pop
down to the fragment itself. This feels like a risky change, and it's
tested only by back-button-dismissing the Wait alert in an unconnected
game in BoardDelegate, so needs some bake time.
When SDK >= 19 there's an API to tell if a listview is full enough to
require scrolling. So use that, rather than the raw count of games, to
decide whether to offer new users to hide the new game buttons.
What's here is merged with the main one, so no point in duplicating and
it's just a pain to maintain. Remove as much as possible that's not
unique to the debug variant (crashlytics and wifidirect, basically.)
Oops. Debug build assertion showed I was leaving an infrequently used
field out of serialization, adn then required that that object be
Serializable and implement equals() to pass more tests.
Get rid of treating 0 as a legit date (1970 being illegit). Tweak
formatting. It's not perfect, but few people see it so we'll see how it
goes during development.
revert a bit, dropping use of git revision and repo to provide an
order. Instead use aapt (which is an ubuntu package now) to pull the
version code and appID from .apk files, order by versionCode, and
secondarily by file mod time.
Auto-update was based on my manually setting what the newest is. Better
to use the git revision stored in the .apk, or failing that (later) in
the file name, to determine "age". This is all based on forcing a linear
order on git commits, but at least at the granularity of releases that
should be ok.
Mistake was putting "download more languages" at the bottom of list of
wordlists in one language. Having separate strings is a bigger change,
and in context just "Download more" works in both cases. Change name of
string so translators will notice.
Was passing through DlgState params a number of classes that weren't
serializable or that didn't have equals() methods required for DEBUG
assertions to pass. Added versions of equals() that just call super in
non-DEBUG case since it's a lot of code and isn't required except for
testing. (Serialization of course is.)
Now that params[] are being bundled all objects passed that way must be
serializeable. And as long as I'm asserting bundle success using
equals() the objects being serialized must implement it.
I wasn't saving/restoring params[] Object array with DlgState, and under
circumstances I can't reproduce that could cause a null object
dereference I was via the Play store. So added a test case that failed
for DlgState instances that have non-null params arrays, and fixed that.
Attempt to fix invite alert sticking around after game connection
finishes, which resulted from the variable pointing to the alert having
been set to null. So stop setting it in onDismiss listener. I think
trying to track it via an iVar is a mistake, that dismiss by tag or
somesuch is the right way to go. But my dialog fragment code isn't up to
that yet.
Add new Utils methods that turn Serializable objects into B64-encoded
Strings, and vice versa. Use in place of existing code, and use anew to
store the array of DeviceID records. Implement the "Delete checked" button.
In Dbg version only (with enabling boolean moved to BuildConfig.)
Currently crashes when first used, and recipient can't always connect to
relay until app rebooted. And of course there are still open questions
like how to populate the list -- how user learns the deviceID of an
opponent.
Was previously a "global" so that a resend over SMS might delay a resend
for relay games. Since communication problems tend to be
comms-type-specific that's dumb.
Added a wrapper function since for compatibility with the jni's encoding
the flags matter and need to be the same everywhere. Or at least there
should be no chance of their getting changed.
As a first step, use mine and android's and assert results are the
same. Note using built-in Base64 class requires moving the min android
version from 7 to 8.
Copy config file into the right place, turn it on for debug builds, and
fix compile-time complaints by adding -keep clauses. Resulting builds
seem to work (after a few minutes in emulator only) except that net
status icon taps crash at first (eventually recovering somehow) likely
because of problems with base64 en/decoding which goes through jni.
Get rid of black background of center icon, instead running the green or
red all the way to the middle. For better contrast make the green and
red darker and the non-active arrows white.
The debug-only settings for disabling individual conn types weren't
kept in sync with which types were enabled, and that set of types was
lost on rotation. Fixed (including keeping the disableds across rotations.)
Since it's now possible to add a name for an SMS contact added manually,
don't assuming a missing name means it was added manually. Just leave
blank if not provided.
I made these changes a while ago then stashed them. They worked when I
needed them then, and seem to work now, but I haven't verified now so
don't trust 'em too far. :-)
Crittercism is dropping free support of FOSS apps, so I'm dropping
it. Unfortunatly integration isn't as clean: so far at least I haven't
figured out how to make it possible for others to build the 'd' variant,
which includes Crashlytics, without their having a Fabric API key.
Resetting it on every ACK etc meant it happened forever at the most
frequent interval, at least on devices that have both sides of a game,
which is my usual test setup.
In game config, when the play game button's hit and the changes pending don't
"matter" (require reset), apply them. The motiviation is to allow
setting the "disableds" and immediately launching a game via the button,
but I think it's the right thing to do anyway. Needs testing.
Deal with occasional droppage of SMS data messages by running a
timer (via AlarmManager) with backoff that resends any pending moves for
games connected via SMS. Successful receipt of SMS data resets the backoff.
cleanup, iterating over built-in data rather than my hard-coded VARIANTS
and BUILDS arrays, which now go away. Renamed obj- and libs- directories
to better fit the variables the system provides.
Add DEBUG-builds-only UI at the bottom of GameConfig for turning on the
comms feature dropping outgoing or incoming messages. The idea's to use
this to reproduce and fix stalls. There's a noticable slowdown opening
the GameConfig fragment, but it should all be no-op in a release build.
Add BuildConfig boolean that's true when a gradle property is passed in
from the fdroid metadata/build file. Test when putting together upgrade
state and omit app info when it's true, same as if from Play store.
F-droid build system modifies git-controlled files so no build will ever
be clean. So look for a -P property and if that's set don't use the
--dirty flag when capturing the git revision. Requires a change to the
fdroid metadata to pass that property.
Years back what I read said you opened and closed the database around
every query. Now I'm reading differently, that the OS will close it for
you on app shutdown and that it's ok to just leave it open. Trying
that after a few minutes on one device looks ok. Will need to test the
heck out of it, especially on older OS versions, before ship.
Play store reports crashes but stack crawl doesn't tell me where
from. So try catching the exception and on debug builds logging what
dialog is responsible.
When those became an advanced feature I added a warning for upgrading
players who'd miss them. That was long ago, and the warning was only
supposed to last a release or two.
showDialog() goes away except for PrefsActivity which can't do
fragments. Move stuff required by PrefsDelegate into it from
DelegateBase since no other subclasses does old-style Dialogs
any more. Remove a bunch of stuff from DlgDelegate, e.g. saving
state, that no longer gets used.
Was getting crash with "Can not perform this action after
onSaveInstanceState". This lets the back stack get back into shape so it
can put up another alert.