Jun 16 | 3:50 PM |
Mark M. | has entered the room |
Mark M. | turned on guest access |
Jun 16 | 3:55 PM |
Randall M. | has entered the room |
Mark M. |
hello, Randall!
|
Mark M. |
how can I help you today?
|
Randall M. |
Hi, Mark!
|
Randall M. |
This is a bit out there, but I really do have a good reason for wanting to do this...
|
Mark M. |
I always get worried when people preface questions like that. :-)
|
Randall M. |
I'm working on a medical application, that communicates via Bluetooth to a cardiac monitor that a patient wears for a 30 day procedure.
|
Randall M. |
heh
|
Randall M. |
The client would like to lock down the device as much as possible, so the patient cannot exit the application or otherwise muck about on the device.
|
Randall M. |
We've already set the "homescreen" app to be this application, but it's will possible to access settings (via swiping down from the top of the screen) and reset this.
|
Jun 16 | 4:00 PM |
Randall M. |
My next plan is to try to detect when the app is moved to the background, and force it back, using some tips from <http://www.andreas-schrade.de/2015/02/16/androi...;, but I'm not having much success.
|
Mark M. |
anything outside of Android 5.0's kiosk mode is not going to be reliable
|
MyWay | has entered the room |
Mark M. |
are you supplying the Android hardware?
|
MyWay |
hi :)
|
Mark M. |
(BTW, hello, MyWay -- I will be with you shortly!)
|
CodeApprentice | has entered the room |
Randall M. |
I've been told "do the best you can". And the target OS is 4.4.2, I think due to the devices they have (Moto Droid RAZR).
|
Mark M. |
(BTW, hello, CodeApprentice -- I will be with you after Randall and MyWay!)
|
CodeApprentice |
Hello
|
Mark M. |
Randall: to be blunt, I wouldn't touch this project with a 10' pole
|
CodeApprentice |
no hurry. Still trying to formulate an intelligent question.
|
Randall M. |
Yeah, it's not the patient's device, they are providing the device. So we can configure it as necessary, but locking it down so the patient cannot change it back is the challenge.
|
Mark M. |
I'd then be looking at the custom ROM direction
|
Randall M. |
Too late, I'm trying to deliver a GM this week so they can start their FDA testing.
|
Mark M. |
"a GM"?
|
Randall M. |
This feature is not FDA required, but their patients have a history of mucking about with the device.
|
Randall M. |
"golden master", "release candidate"...
|
Mark M. |
ah
|
Randall M. |
I've not worked much with 5.0 yet...is that pretty reliably locked down?
|
Mark M. |
it's more that kiosk mode is actually part of the OS
|
Mark M. |
as opposed to, um, hacks to try to fake it
|
Randall M. |
That is likely a future direction, but because of hardware on hand, we can't go there yet.
|
Jun 16 | 4:05 PM |
Randall M. |
At this point, they're prety OK with hacks, even if not 100% reliable.
|
Mark M. |
that being said, I don't really know what to tell you
|
Mark M. |
I avoid getting into this sort of area
|
Mark M. |
I recognize that your use case for this sort of stuff is legitimate
|
Randall M. |
The app is able to recover from crashes and resume the procedure, but the problem I've encountered with the hack in the aforementioned link is, when it tries to restart the Activity, it starts it over from scratch, losing all state data that's important.
|
Randall M. |
OK, I understand. I've been working on this a couple days now, and decided to leave no stone unturned. :)
|
Mark M. |
I really apologize -- I try not to exclude topics, but this is one
|
Randall M. |
I'm OK telling them that all they want cannot be done, but I want to make sure I perform my due diligence first.
|
Mark M. |
let me take questions from the others, and if you can come up with another take on this that doesn't frighten me, go ahead :-)
|
Mark M. |
I'll be back with you shortly
|
Randall M. |
Thanks.
|
Mark M. |
MyWay: your turn! do you have a question?
|
MyWay |
View paste
|
Jun 16 | 4:10 PM |
Mark M. |
hmmm, I thought that's what transcriptMode normal did
|
Mark M. |
regardless, you'd need to detect that you are not showing the bottom chat entries
|
Mark M. |
probably an OnScrollListener and getLastVisiblePosition() on the ListView would handle that
|
Mark M. |
your chat is going to be using a background thread for incoming messages, so somewhere you're passing those over to the main application thread
|
Mark M. |
that logic would check the state and, if you're not at the bottom, queue up the messages
|
Mark M. |
when the OnScrollListener says "hey, we're at the bottom!", you'd add all the queued messages
|
Mark M. |
personally, I am somewhat skeptical that this is going to give you a good UX though
|
MyWay |
yes, so I stop passing them and when I'm at bottom, I load them
|
Mark M. |
and I haven't tried this, so my recipe may have flaws
|
Mark M. |
but it's where I'd start if I had the need for trying something like this
|
MyWay |
the bad thing is that you're reading old messages, but you can't, because new messages are coming up
|
MyWay |
or you want to tap on a message and you can't, for the same reason
|
MyWay |
so I had the idea to stop sending new messages
|
MyWay |
maybe I can place something at the bottom saying that there are new messages for the UX
|
Jun 16 | 4:15 PM |
Mark M. |
or at least do that if the number of queued messages exceeds some threshold
|
Mark M. |
dumping dozens of new lines in at one shot without warning may be confusing, but adding one or two might not be bad
|
Mark M. |
let me take questions from the others, and I will swing back to you in a bit
|
Mark M. |
CodeApprentice: your turn! do you have a question?
|
MyWay |
yes, thank you
|
CodeApprentice |
Have you had a chance to look at Espresso since the last time we talked?
|
Mark M. |
nope
|
Mark M. |
got swamped by a macademia nut cookie
|
CodeApprentice |
well, that's a good reason ;-)
|
Mark M. |
it's now in the "I really need to get to this, but I have no idea when my schedule will support it" bucket
|
CodeApprentice |
I'm there as well. I want to look at using it for my existing app.
|
CodeApprentice |
But I have other bugs and features that are a bit more pressing.
|
Mark M. |
I forget how far along Chiu-Ki Chan is with her Espresso book
|
Mark M. |
ah, OK, she's not as far along as I had been thinking
|
CodeApprentice |
I'll have to look into that.
|
Mark M. |
FWIW, she has a bunch of samples at https://github.com/chiuki/espresso-samples
|
CodeApprentice |
Rather than typing out a question, can I give an SO link?
|
Mark M. |
sure
|
Jun 16 | 4:20 PM |
CodeApprentice | |
Jun 16 | 4:20 PM |
CodeApprentice |
That's my most pressing issue right now.
|
CodeApprentice |
Basically it boils down to: how do I force all other threads to wait for a signal from another thread that they can continue?
|
Mark M. |
hmmm...
|
Mark M. |
the problem is that your LoaderManager stuff is production code, not test code, right?
|
CodeApprentice |
I'm a multithreading noob, so I'm having a hard time wrapping my head around this.
|
CodeApprentice |
yes, LoaderManager is production
|
Mark M. |
so you really don't want to be adding in crap to it that is testing-specific if possible
|
CodeApprentice |
right
|
Mark M. | |
CodeApprentice |
and to be specific. I'm using the default LoaderManager and CursorLoader implementations and provide a LoaderCallbacks implementation
|
Mark M. |
yeah, to be honest, I haven't a clue off the top of my head
|
Mark M. |
this is one of the reasons I don't fuss with GUI testing, doing my tests more at the model/controller level
|
Mark M. |
I'd test to confirm the Cursor, or maybe even the CursorLoader, comes back with what I want
|
CodeApprentice |
I try to do that as well.
|
Jun 16 | 4:25 PM |
Mark M. |
toss a bounty on your SO question and hope somebody gives it some love, I guess
|
Mark M. |
I'm sure there's a recipe for it
|
CodeApprentice |
I'll consider that...although, I'm getting close to 20k, so I want to be sparing with bounties.
|
Mark M. |
let me take questions from the others, and I'll be back with you in a bit
|
CodeApprentice |
kk
|
Mark M. |
Randall: back to you! do you have another question?
|
Randall M. |
Almost...can I defer, and you come back to me?
|
Mark M. |
sure
|
Mark M. |
let me know when you're ready so I can put you back into the queue
|
Mark M. |
MyWay: back to you! do you have another question?
|
Randall M. |
OK, ready when you are...
|
Mark M. |
Randall: go ahead
|
Jun 16 | 4:30 PM |
Randall M. |
OK, so I'm trying to "restore" an Activity when a Service I've created (running every 2 seconds) detects that my app is no longer in the foreground. The same code I have shows starting the activity with a FLAG_ACTIVITY_NEW_TASK flag, which looks like it compete recreates the activity, rather than simply restarting it.
|
Randall M. |
is FLAG_ACTIVITY_BROUGHT_TO_FRONT what I want instead?
|
Randall M. |
Or maybe FLAG_ACTIVITY_CLEAR_TOP?
|
Mark M. |
well, I don't think you have a choice when calling startActivity() from a service
|
Mark M. |
I think you have to use FLAG_ACTIVITY_NEW_TASK
|
Randall M. |
And does this work for activities that are not part of my app?
|
Randall M. |
Oh. Hmmm...
|
Randall M. |
Bother.
|
Randall M. |
I think that kills that idea then.
|
Mark M. |
usually, if you are starting an activity from something other than an activity, you need FLAG_ACTIVITY_NEW_TASK
|
Mark M. |
is this service in the same process as the activity?
|
Randall M. |
Yes.
|
Mark M. |
then don't have the service call startActivity(0
|
Randall M. |
Is it possible to store the current Activity in an ivar, and manually call start() (or resume()?) on it, from the service?
|
Mark M. |
er, startActivity()
|
Mark M. |
no, in part because there is no start() or resume()
|
Mark M. |
and you don't want to put a reference to an Activity in a static data member (my interpretation of "ivar")
|
Mark M. |
due to memory leaks
|
Mark M. |
you could use an in-process event bus to let the UI layer know "hey, we're in the background -- do something"
|
Mark M. |
LocalBroadcastManager, Square's Otto, and greenrobot's EventBus are the three big event buses
|
Jun 16 | 4:35 PM |
Randall M. |
"then don't have the service call startActivity()"..."hey, we're in the background -- do something"...Yeah, what's that "something"?
|
Mark M. |
if your activity still exists, it can call startActivity() with flags that are more apropos to your use case
|
Mark M. |
if the service determines that nobody handled the event, then you know that your activity is flat-out gone (e.g., unhandled exception), and you should be restarting it from scratch with startActivity() and FLAG_ACTIVITY_NEW_TASK anyway
|
Randall M. |
Ah, cool....OK, I'll give that a shot.
|
Mark M. |
let me take questions from the others, and I'll be back with you in a bit
|
Mark M. |
MyWay: do you have another question?
|
Randall M. |
Sure thing.
|
MyWay |
maybe I can stop the messages as we were saying, add a info message/button to go back to the bottom and add to the top something like "load older messages" like some app is doing, to avoid users losing new messages and showing them too much, what do you think Mark? And if yes, how can I do that thing "load older messages"?
|
Mark M. |
well, for the "load older messages", I'd be tempted to say "pull to refresh", but your direction is inverted from the norm there
|
Mark M. |
usually you pull from the top to add things to the top
|
Mark M. |
beyond that, have a widget that you hide and show, or use a snackbar
|
Mark M. |
or use a crouton
|
Mark M. |
(snackbar is in the Android Design Support Library, crouton is an open source library)
|
Jun 16 | 4:40 PM |
MyWay |
yes
|
Mark M. |
or other similar sorts of alert-y things: http://android-arsenal.com/tag/104
|
MyWay |
to load older messages I'd have to put it at the top
|
MyWay |
thank you for your help, I think it can work this way
|
Mark M. |
OK
|
Mark M. |
let me give the others another shot, and I'll return to you in a bit
|
Mark M. |
CodeApprentice: do you have another question?
|
Mark M. |
CodeApprentice: if you come up with another question, let me know
|
Mark M. |
Randall: back to you! do you have another question?
|
Randall M. |
Yeah, so...
|
CodeApprentice |
Getting some info together for you and trying to formulate my question.
|
CodeApprentice |
still thinking about the synchronization issue
|
Randall M. |
I tried
|
Randall M. |
View paste
|
Randall M. |
And got
|
Randall M. |
Exception: "Calling startActivity() from outside of an Activity context requires the FLAG_ACTIVITY_NEW_TASK flag. Is this really what you want?"
|
Mark M. |
right
|
Randall M. |
So, do I understand that correctly, that even though in the same task, I can't do that?
|
Mark M. |
that's because context is not an instance of Activity
|
Jun 16 | 4:45 PM |
Randall M. |
Or am I not in the same task?
|
Jun 16 | 4:45 PM |
Mark M. |
a Context that is not an Activity has no task
|
Mark M. |
tasks are purely an activity thing
|
Randall M. |
Ah, so hence your suggestion for a broadcast, received by a context that is an Activity, and *that* activity restarts the other one?
|
Mark M. |
right
|
Mark M. |
though don't use a system broadcast; use an in-process event bus
|
Randall M. |
OK, I think I get it now.
|
Mark M. |
so LocalBroadcastManager would be fine, if you're into broadcasts and IntentFilter and such
|
Randall M. |
Is there some flag I can set to tell the system to try to hold onto a given Activity for as long as possible?
|
CodeApprentice |
let me know when you're ready for my question, Mark
|
Randall M. |
(to try to prevent it from being destroyed before my 2-second service execution)?
|
Mark M. |
Randall: not really
|
Mark M. |
the only reason it would be destroyed is if you finished it, the user pressed BACK, or you crashed with an unhandled exception
|
Mark M. |
Android does not randomly destroy activities, though it does terminate *processes* due to low memory conditions
|
Randall M. |
Ah, OK then. I'll go down this path, and see what happens.
|
Randall M. |
You might see my Thursday evening. :)
|
Mark M. |
OK
|
Mark M. |
CodeApprentice: OK, back to you
|
CodeApprentice |
First, the relevant classes in my github:
|
CodeApprentice |
View paste
|
CodeApprentice |
hmm...didn't make links, sorry
|
Mark M. |
that's OK
|
Mark M. |
I can copy and paste with the best of 'em :-)
|
CodeApprentice |
So one of the tests in BaseballCardListWithoutDataTest.java adds some data and then returns to the ListView to make sure the data is displayed correctly.
|
CodeApprentice |
but the data is now being loaded with LoaderManager and CursorLoader
|
Jun 16 | 4:50 PM |
CodeApprentice |
so I need the test to wait for the loader to finish populating the listview
|
CodeApprentice |
my idea for a solution is to add a `waitForData()` method to BaseballCardList which simply delegates the call to BaseballCardLoaderCallbacks.waitForData().
|
CodeApprentice |
the place I am stuck is implementing this last method
|
Mark M. |
ah, that would explain why I can't find it :-)
|
CodeApprentice |
yah, I haven't committed anything that I've tried yet.
|
Mark M. |
well, if you're willing to clutter your production code with test-related stuff, use a CountDownLatch or something
|
Mark M. |
that's my go-to class in testing where I need to block the test thread waiting for some other async operation
|
CodeApprentice |
I'm trying to minimize the amount of clutter in production code, but this is the best I've been able to come up with so far.
|
Mark M. |
understood -- as I indicated earlier, I don't know how to solve this without some degree of production clutter either
|
CodeApprentice |
so if I understand CountDownLatch correctly, I will need to create a new one each time the loading starts, correct?
|
Mark M. |
yes, that sounds right
|
Mark M. |
another more flexible approach would be to use more of a listener pattern
|
CodeApprentice |
hmm...
|
Mark M. |
BaseballCardLoaderCallbacks would tell listener(s) "hey, the load is done"
|
Jun 16 | 4:55 PM |
Mark M. |
your test code would hook up a listener that would countDown() the latch
|
Mark M. |
and your main test method would await() the latch
|
Mark M. |
now, in this case, the listener is only needed by testing, so it's technically clutter
|
CodeApprentice |
what do you mean by "test code" and "main test method"?
|
Mark M. |
actually, they're the same thing, sorry
|
Mark M. |
let's say you have testThatMyListGetsItsCards()
|
Mark M. |
which is the test method that is trying to do this whole wait-on-the-loader thing
|
CodeApprentice |
I'm with you so far.
|
Mark M. |
testThatMyListGetsItsCards() would somehow (and I'm fuzzy as to how) register a listener with BaseballCardsLoaderCallback, where that listener would countDown() the latch
|
Mark M. |
then, testThatMyListGetsItsCards() would trigger the actual data load, then await() the latch
|
Mark M. |
BaseballCardsLoaderCallback would invoke the listener's event method (e.g., yesWeHaveCards())
|
Mark M. |
that listener is where the countDown() is
|
Mark M. |
net: testThatMyListGetsItsCards() blocks until the load is done
|
Mark M. |
problem #1: how do you get the BaseballCardsLoaderCallback to register the listener?
|
Mark M. |
problem #2: is that still too soon for your test results (e.g., is the ListView updated by now)?
|
Mark M. |
but I leave those as an exercise for the reader :-)
|
Jun 16 | 5:00 PM |
CodeApprentice |
I'm worried about a scenario where someone await()s but the loader is restarted. If I simply create a new CountDownLatch each time the loader restarts, then there could be a problem that the await() never returns.
|
Mark M. |
this is test code
|
Mark M. |
make sure you unregister the listener when the test completes
|