Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prompt at the end of a trip to report incidents #229

Closed
shankari opened this issue Dec 2, 2016 · 65 comments
Closed

Prompt at the end of a trip to report incidents #229

shankari opened this issue Dec 2, 2016 · 65 comments

Comments

@shankari
Copy link
Contributor

shankari commented Dec 2, 2016

While its great to have the ability to report incidents, that's already in see-click-fix and other such platforms. What happens is that the incident happens, and then you get back and you forget to record it. We want to start prompting people to record incidents at the end of a trip.

@shankari
Copy link
Contributor Author

shankari commented Dec 2, 2016

Step 1:

  • See if we can listen to the broadcast notifications in javascript and generate the notification from there, to avoid having to duplicate code. In particular, on android, where the service can be opened without the UI, will the javascript still be triggered.

Tests:

  • install a version of the app that listens to and generates the notifications on both android and ios. android version rebroadcasts to local broadcast handler to make it possible to use the localbroadcast cordova plugin
    • Run it without force killing android. we can assume that iOS is not force killed because we prompt to turn it on when it is killed.

@shankari
Copy link
Contributor Author

shankari commented Dec 3, 2016

Without force killing android, notifications are generated correctly.
With force killing android, notifications don't appear to be generated.
iOS doesn't have force killing anyway and works just fine.

Checking logs on android.

@shankari
Copy link
Contributor Author

shankari commented Dec 3, 2016

Logs are at:
killed_emission_local_broadcast.gz

Yeah, the trip end was detected.

12-02 17:14:12.402  1460  3764 D LocationChangeIntentService: last5MinsSpan = 278.80200004577637 secs, threshold + fuzz = 270
12-02 17:14:12.410  1460  3764 D LocationChangeIntentService: maxDistance = 24.76055908203125 TRIP_END_RADIUS = 100
12-02 17:14:12.418  1460  3764 D LocationChangeIntentService: stoppedMoving: stoppedMoving = true
12-02 17:14:12.427  1460  3764 D LocationChangeIntentService: maxDistance = 24.76055908203125 TRIP_END_RADIUS = 100
12-02 17:14:12.436  1460  3764 D LocationChangeIntentService: stoppedMoving: stoppedMoving = true
12-02 17:14:12.445  1460  3764 I LocationChangeIntentService: isTripEnded: stoppedMoving = true

Receiver received it - invoked service. Clearly no local notification was received. Need to add more logging to see exactly what happened. Will the more sophisticated bridge launch the webview? Need to experiment with it...

12-02 17:14:12.474  1460  1460 I TripDiaryStateMachineReceiver: TripDiaryStateMachineReciever onReceive(android.app.ReceiverRestrictedContext@ab09066, Intent { act=local.transition.stopped_moving flg=0x10 cmp=edu.berkeley.eecs.emission/.cordova.tracker.location.TripDiaryStateMachineReceiver (has extras) }) called
12-02 17:14:12.490  1460  1460 I TripDiaryStateMachineService: Service created. Initializing one-time variables!
12-02 17:14:12.510  1460  1460 D TripDiaryStateMachineService: service started with flags = 0 startId = 1 action = local.transition.stopped_moving

@shankari
Copy link
Contributor Author

shankari commented Dec 6, 2016

Even on iOS, noticed big differences with control phone. Noticed that there were big gaps in collection. This is consistent with crashes and then restarting tracking again through significant location changes. Let's see if there are any crashes reported on the phone.

Example 1 Example 2
screen shot 2016-12-05 at 5 06 21 pm screen shot 2016-12-05 at 5 05 35 pm

@shankari
Copy link
Contributor Author

shankari commented Dec 6, 2016

Yup! Crash roughly 30 mins.

screen shot 2016-12-05 at 5 26 20 pm

@shankari
Copy link
Contributor Author

shankari commented Dec 6, 2016

Exception:

Incident Identifier: CBBF4847-027A-4CBD-9E7F-7FD85D552166
CrashReporter Key:   fe64d46fae153cdcadc5fa3c84dcfeb190509f12
Hardware Model:      iPhone7,2
Process:             emission [1565]
Path:                /private/var/containers/Bundle/Application/37408512-7314-4EE6-8AF4-392EE3B22997/emission.app/emission
Identifier:          edu.berkeley.eecs.emission
Version:             20 (1.6.0)
Code Type:           ARM-64 (Native)
Parent Process:      launchd [1]

Date/Time:           2016-12-05 17:00:23.23 -0800
Launch Time:         2016-12-05 16:50:07.07 -0800
OS Version:          iOS 9.3 (13E233)
Report Version:      105

Exception Type:  EXC_CRASH (SIGABRT)
Exception Codes: 0x0000000000000000, 0x0000000000000000
Exception Note:  EXC_CORPSE_NOTIFY
Triggered by Thread:  0

Filtered syslog:
None found

Last Exception Backtrace:
0   CoreFoundation                	0x1829f6e38 __exceptionPreprocess + 124
1   libobjc.A.dylib               	0x18205bf80 objc_exception_throw + 56
2   CoreFoundation                	0x1829f6d80 +[NSException raise:format:] + 120
3   Foundation                    	0x1834431d4 _writeJSONValue + 700
4   Foundation                    	0x183445364 ___writeJSONObject_block_invoke + 236
5   CoreFoundation                	0x18294250c __65-[__NSDictionaryI enumerateKeysAndObjectsWithOptions:usingBlock:]_block_invoke + 88
6   CoreFoundation                	0x182930184 -[__NSDictionaryI enumerateKeysAndObjectsWithOptions:usingBlock:] + 180
7   Foundation                    	0x183444af0 _writeJSONObject + 360
8   Foundation                    	0x1834430a0 _writeJSONValue + 392
9   Foundation                    	0x183442ed0 -[_NSJSONWriter dataWithRootObject:options:error:] + 140
10  Foundation                    	0x183443f2c +[NSJSONSerialization dataWithJSONObject:options:error:] + 348
11  emission                      	0x1000e700c -[CDVBroadcaster fireEvent:data:] (CDVBroadcaster.m:74)
12  emission                      	0x1000e7a7c __35-[CDVBroadcaster addEventListener:]_block_invoke (CDVBroadcaster.m:121)
13  Foundation                    	0x1832e1558 -[__NSObserver _doit:] + 308
14  CoreFoundation                	0x182998eac __CFNOTIFICATIONCENTER_IS_CALLING_OUT_TO_AN_OBSERVER__ + 20
15  CoreFoundation                	0x1829986cc _CFXRegistrationPost + 396
16  CoreFoundation                	0x18299844c ___CFXNotificationPost_block_invoke + 60
17  CoreFoundation                	0x182a01494 -[_CFXNotificationRegistrar find:object:observer:enumerator:] + 1532
18  CoreFoundation                	0x1828d6788 _CFXNotificationPost + 368
19  Foundation                    	0x1832de89c -[NSNotificationCenter postNotificationName:object:userInfo:] + 68
20  emission                      	0x10007a2a4 -[AppDelegate(notification) launchTripEndCheckAndRemoteSync:] (BEMAppDelegate.m:179)
21  emission                      	0x100079824 -[AppDelegate(notification) application:didReceiveRemoteNotification:fetchCompletionHandler:] (BEMAppDelegate.m:86)
22  UIKit                         	0x187dfa0b8 -[UIApplication _handleNonLaunchSpecificActions:forScene:withTransitionContext:completion:] + 2676
23  UIKit                         	0x187dffab4 __88-[UIApplication _handleApplicationLifecycleEventWithScene:transitionContext:completion:]_block_invoke + 188
24  UIKit                         	0x187dff990 -[UIApplication _handleApplicationLifecycleEventWithScene:transitionContext:completion:] + 448
25  UIKit                         	0x187de961c __70-[UIApplication scene:didUpdateWithDiff:transitionContext:completion:]_block_invoke + 152
26  UIKit                         	0x187de92a4 -[UIApplication scene:didUpdateWithDiff:transitionContext:completion:] + 712
27  FrontBoardServices            	0x1843977ac __FBSSERIALQUEUE_IS_CALLING_OUT_TO_A_BLOCK__ + 36
28  FrontBoardServices            	0x184397618 -[FBSSerialQueue _performNext] + 168
29  FrontBoardServices            	0x1843979c8 -[FBSSerialQueue _performNextFromRunLoopSource] + 56
30  CoreFoundation                	0x1829ad124 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 24
31  CoreFoundation                	0x1829acbb8 __CFRunLoopDoSources0 + 540
32  CoreFoundation                	0x1829aa8b8 __CFRunLoopRun + 724
33  CoreFoundation                	0x1828d4d10 CFRunLoopRunSpecific + 384
34  GraphicsServices              	0x1841bc088 GSEventRunModal + 180
35  UIKit                         	0x187ba9f70 UIApplicationMain + 204
36  emission                      	0x10000baf8 main (main.m:32)
37  libdyld.dylib                 	0x1824728b8 start + 4

@shankari
Copy link
Contributor Author

shankari commented Dec 6, 2016

Failure is here:

    NSString *jsonDataString = @"{}";
    
    if( data  ) {
        
        NSError *error;
        NSData *jsonData = [NSJSONSerialization dataWithJSONObject:data
                                                           options:(NSJSONWritingOptions)0
                                                             error:&error];
        
        if (! jsonData) {

            throwWithName(error, @"JSON Serialization exception");
            return;
            
        }

@shankari
Copy link
Contributor Author

shankari commented Dec 6, 2016

Able to get there with the debugger. Let's see what's going on now.

note	NSConcreteNotification *	@"TRANSITION_NAME"	0x000000013ff602e0
        NSNotification	NSNotification	
name	__NSCFConstantString *	@"TRANSITION_NAME"	0x000000010022c8d0
object	__NSCFConstantString *	@"T_DATA_PUSHED"	0x000000010022d070
userInfo	id	0x0	0x0000000000000000
dyingObject	bool	false

Didn't crash when connected to the debugger.

@shankari
Copy link
Contributor Author

shankari commented Dec 6, 2016

Ah, this time, for the RECEIVED_SILENT_PUSH transition, the data = userInfo = the handler which cannot be serialized as JSON. It will probably crash now.

note	NSConcreteNotification *	@"TRANSITION_NAME"	0x000000013fee9f50
        NSNotification	NSNotification	
name	__NSCFConstantString *	@"TRANSITION_NAME"	0x000000010022c8d0
object	__NSCFConstantString *	@"T_RECEIVED_SILENT_PUSH"	0x000000010022cb50
userInfo	__NSDictionaryI *	1 key/value pair	0x000000013ff30d50
[0]	(null)	@"handler" : (no summary)	
key	__NSCFConstantString *	@"handler"	0x000000010022ce50
value	__NSMallocBlock__ *	0x13fde1a40	0x000000013fde1a40

@shankari
Copy link
Contributor Author

shankari commented Dec 6, 2016

Yup! data =

data	__NSDictionaryI *	1 key/value pair	0x000000013ff30d50
[0]	(null)	@"handler" : (no summary)	
key	__NSCFConstantString *	@"handler"	0x000000010022ce50
value	__NSMallocBlock__ *	0x13fde1a40	0x000000013fde1a40

crashed!

Solution is to not listen to the RECEIVED_SILENT_PUSH notification. Much easier to do with the new plugin.

@shankari
Copy link
Contributor Author

shankari commented Dec 6, 2016

Added additional logging in the cordova broadcaster code.
Took a trip from 10:30 to 12:30 for volunteering, and then again from 5:30 to 7:30 to see tree lighting. Here are the relevant logs from the dumped logcat (attached).

Installed version with additional logging.

12-05 09:56:22.676  2543  2556 I ActivityManager: Force stopping edu.berkeley.eecs.emissi
on appid=10106 user=-1: uninstall pkg
12-05 09:56:23.101  2543  2566 I ActivityManager: Force stopping edu.berkeley.eecs.emissi
on appid=10106 user=0: pkg removed
12-05 09:56:24.394  2543  3503 I ActivityManager: START u0 {act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10000000 cmp=edu.berkeley.eecs.emission/.MainActivity} from uid 2000 on display 0
12-05 09:56:24.419  2543  2554 I ActivityManager: Start proc 24506:edu.berkeley.eecs.emission/u0a106 for activity edu.berkeley.eecs.emission/.MainActivity

I force killed the app to avoid having it in the background.

12-05 09:58:02.740  2543  3503 I ActivityManager: Killing 24506:edu.berkeley.eecs.emission/u0a106 (adj 9): remove task
12-05 09:58:02.911  2543  3196 I WindowState: WIN DEATH: Window{8aa2349 u0 edu.berkeley.eecs.emission/edu.berkeley.eecs.emission.MainActivity}
12-05 09:58:02.911  2543  3196 W WindowManager: Force-removing child win Window{334a0ec u0 SurfaceView} from container Window{8aa2349 u0 edu.berkeley.eecs.emission/edu.berkeley.eecs.emission.MainActivity}

Next call to the TripDiaryStateMachine is at 7:32. Maybe that was when I turned
duty cycling back on? It was in ongoing_trip until that point.

12-05 19:32:16.363  4040  4040 I TripDiaryStateMachineReceiver: noarg constructor called
12-05 19:32:16.379  4040  4040 I TripDiaryStateMachineReceiver: TripDiaryStateMachineReciever onReceive(android.app.ReceiverRestrictedContext@c14e56b, Intent { act=local.transition.stopped_moving flg=0x10 cmp=edu.berkeley.eecs.emission/.cordova.tracker.location.TripDiaryStateMachineReceiver (has extras) }) called

12-05 19:32:16.458  4040  4040 D TripDiaryStateMachineService: service started with flags = 0 startId = 1 action = local.transition.stopped_moving
12-05 19:32:16.470  4040  4040 D TripDiaryStateMachineService: after reading from the prefs, the current state is local.state.ongoing_trip

Launched again

12-05 20:03:03.247  4024  7505 I System.out: All preferences are {setup_complete=202, TripDiaryCurrState=local.state.waiting_for_trip_start}
12-05 20:03:03.256  4024  7505 D TripDiaryStateMachineReceiver: Setup complete, skipping initialize

@shankari
Copy link
Contributor Author

shankari commented Dec 6, 2016

First instances of LocationChangeIntentService are at:

12-05 19:27:28.750  4040  7364 D LocationChangeIntentService: last5MinsSpan = 270.0 secs, threshold + fuzz = 270
12-05 19:27:28.765  4040  7364 D LocationChangeIntentService: maxDistance = 246.25845336914063 TRIP_END_RADIUS = 100
12-05 19:27:28.776  4040  7364 D LocationChangeIntentService: stoppedMoving: stoppedMoving = false
12-05 19:27:28.784  4040  7364 I LocationChangeIntentService: isTripEnded: stoppedMoving = false

before the geofence exit (at 9:56). Unsure they got started, but it looks like they ran for a while and then we detected that the trip stopped. What about the trip to school?

@shankari
Copy link
Contributor Author

shankari commented Dec 6, 2016

here are the additional logs from the broadcaster. Note that they only start after

12-05 19:32:16.387  4040  4040 D CDVBroadcaster: Received broadcast in native code
12-05 19:32:16.397  4040  4040 D CDVBroadcaster: Firing event local.transition.stopped_moving
12-05 19:32:16.404  4040  4040 D CDVBroadcaster: Sending javascript to webView org.apache.cordova.CordovaWebViewImpl@8bddb2e

@shankari
Copy link
Contributor Author

shankari commented Dec 6, 2016

Added additional logging in the cordova broadcaster code.
Took a trip from 10:30 to 12:30 for volunteering, and then again from 5:30 to 7:30 to see tree lighting. Here are the relevant logs from the dumped logcat.

android_localbroadcast.log.gz

Installed version with additional logging

12-05 09:56:22.676  2543  2556 I ActivityManager: Force stopping edu.berkeley.eecs.emissi
on appid=10106 user=-1: uninstall pkg
12-05 09:56:23.101  2543  2566 I ActivityManager: Force stopping edu.berkeley.eecs.emissi
on appid=10106 user=0: pkg removed
12-05 09:56:24.394  2543  3503 I ActivityManager: START u0 {act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10000000 cmp=edu.berkeley.eecs.emission/.MainActivity} from uid 2000 on display 0
12-05 09:56:24.419  2543  2554 I ActivityManager: Start proc 24506:edu.berkeley.eecs.emission/u0a106 for activity edu.berkeley.eecs.emission/.MainActivity

I force killed the app to avoid having it in the background.

12-05 09:58:02.740  2543  3503 I ActivityManager: Killing 24506:edu.berkeley.eecs.emission/u0a106 (adj 9): remove task
12-05 09:58:02.911  2543  3196 I WindowState: WIN DEATH: Window{8aa2349 u0 edu.berkeley.eecs.emission/edu.berkeley.eecs.emission.MainActivity}
12-05 09:58:02.911  2543  3196 W WindowManager: Force-removing child win Window{334a0ec u0 SurfaceView} from container Window{8aa2349 u0 edu.berkeley.eecs.emission/edu.berkeley.eecs.emission.MainActivity}

Next call to the TripDiaryStateMachine is at 7:32. Maybe that was when I turned
duty cycling back on? It was in ongoing_trip until that point.

12-05 19:32:16.363  4040  4040 I TripDiaryStateMachineReceiver: noarg constructor called
12-05 19:32:16.379  4040  4040 I TripDiaryStateMachineReceiver: TripDiaryStateMachineReciever onReceive(android.app.ReceiverRestrictedContext@c14e56b, Intent { act=local.transition.stopped_moving flg=0x10 cmp=edu.berkeley.eecs.emission/.cordova.tracker.location.TripDiaryStateMachineReceiver (has extras) }) called

12-05 19:32:16.458  4040  4040 D TripDiaryStateMachineService: service started with flags = 0 startId = 1 action = local.transition.stopped_moving
12-05 19:32:16.470  4040  4040 D TripDiaryStateMachineService: after reading from the prefs, the current state is local.state.ongoing_trip

Launched again

12-05 20:03:03.247  4024  7505 I System.out: All preferences are {setup_complete=202, TripDiaryCurrState=local.state.waiting_for_trip_start}
12-05 20:03:03.256  4024  7505 D TripDiaryStateMachineReceiver: Setup complete, skipping initialize

The only time the new logging was triggered was at around 7:30

12-05 19:32:16.387  4040  4040 D CDVBroadcaster: Received broadcast in native code
12-05 19:32:16.397  4040  4040 D CDVBroadcaster: Firing event local.transition.stopped_moving
12-05 19:32:16.404  4040  4040 D CDVBroadcaster: Sending javascript to webView org.apache.cordova.CordovaWebViewImpl@8bddb2e

@shankari
Copy link
Contributor Author

shankari commented Dec 6, 2016

Let's confirm that data collection settings are all set correctly. Looking at the diary from yesterday, after turning duty cycling on, the data was pushed and processed correctly. I now see 5 trips yesterday, including one from 10:44 -> 10:52 and one from 12:29 -> 12:41.

Let me see what the logs show about data collection at that time.

@shankari
Copy link
Contributor Author

shankari commented Dec 6, 2016

The first LocationChangeIntentService in the logs was at 19:27.
But we must have got locations because we see the trips on the phone.
So maybe the logs got overwritten/are not very accurate?

Let me export the emission logs instead...

$ zgrep LocationChangeIntentService /tmp/android_localbroadcast.log.gz | head -n 5
12-05 19:27:28.750  4040  7364 D LocationChangeIntentService: last5MinsSpan = 270.0 secs, threshold + fuzz = 270
12-05 19:27:28.765  4040  7364 D LocationChangeIntentService: maxDistance = 246.25845336914063 TRIP_END_RADIUS = 100
12-05 19:27:28.776  4040  7364 D LocationChangeIntentService: stoppedMoving: stoppedMoving = false
12-05 19:27:28.784  4040  7364 I LocationChangeIntentService: isTripEnded: stoppedMoving = false
12-05 19:27:58.141  4040  4040 D LocationChangeIntentService: onCreate called

@shankari
Copy link
Contributor Author

shankari commented Dec 6, 2016

Walking around makes it so that we can't really set breakpoints, etc.
Instead, I created a new test case that broadcasts the end of trip notification.
I first tried to add this as a test to the e-mission-data-collection repo, but found that shoehorning native tests into the cordova projects was non-trivial. I should look at examples of native tests in the standard cordova plugins.

In the meanwhile, I wrote a separate test case in a separate, temporary project.
The test case looks like this

    public void testSendEndTransition() {
        Context ctxt = getSystemContext();
        Intent stopMonitoringIntent = new Intent();
        stopMonitoringIntent.setAction("local.transition.stopped_moving");
        Location dummyLoc = new Location("dummy");
        dummyLoc.setLatitude(37.00);
        dummyLoc.setLongitude(-122.00);
        dummyLoc.setAltitude(10.00);
        stopMonitoringIntent.putExtra(FusedLocationProviderApi.KEY_LOCATION_CHANGED, dummyLoc);
        ctxt.sendBroadcast(stopMonitoringIntent);
    }

I can confirm that running the test causes this breakpoint in CDVBroadcaster.java to be invoked when the app is running. Now to kill the app and see what happens...

            if (!receiverMap.containsKey(eventName)) {

                final BroadcastReceiver r = new BroadcastReceiver() {

                    @Override
                    public void onReceive(Context context, final Intent intent) {

                        Log.d(cordova.getActivity(), TAG, "Received broadcast in native code");
                        final Bundle b = intent.getExtras();

                        // parse the JSON passed as a string.
                        try {

                            String userData = "{}";
                            if (b != null) {//  in some broadcast there might be no extra info
                                userData = b.getString(USERDATA, "{}");
                            } else {
                                Log.d(cordova.getActivity(), TAG, "No extra information in intent bundle");
                            }
                            fireEvent(eventName, userData);

                        } catch (JSONException e) {
                            Log.e(cordova.getActivity(), TAG, "'userdata' is not a valid json object!");
                        }

                    }
                };

@shankari
Copy link
Contributor Author

shankari commented Dec 6, 2016

We don't know whether the local broadcast manager will be invoked when the app is killed, so let's also add a callback in the broadcast manager, which we know will work.

Works.

screen shot 2016-12-06 at 11 24 41 am

screen shot 2016-12-06 at 11 24 27 am

@shankari
Copy link
Contributor Author

shankari commented Dec 6, 2016

Now, I kill the app and re-run the test. Logs are here.
android_localbroadcast_tests.log.gz

It is pretty clear that CDVBroadcaster is not invoked when the app is restarted.

Working case

Here's a working case from the previous successful run at 11:25

12-06 11:24:39.175  4040  4040 I TripDiaryStateMachineReceiver: TripDiaryStateMachineReciever onReceive(android.app.ReceiverRestrictedContext@c14e56b, Intent { act=local.transition.stopped_moving flg=0x10 cmp=edu.berkeley.eecs.emission/.cordova.tracker.location.TripDiaryStateMachineReceiver (has extras) }) called


12-06 11:24:39.185  4040  4040 D CDVBroadcaster: Received broadcast in native code
12-06 11:24:55.937  4040  4040 D CDVBroadcaster: Firing event local.transition.stopped_moving
12-06 11:24:57.640  4040  4040 D CDVBroadcaster: Sending javascript to webView org.apache.cordova.CordovaWebViewImpl@8bddb2e


12-06 11:24:59.278  4040  4040 D TripDiaryStateMachineService: service started with flags = 0 startId = 4 action = local.transition.stopped_moving
12-06 11:24:59.291  4040  4040 D TripDiaryStateMachineService: after reading from the prefs, the current state is local.state.waiting_for_trip_start
12-06 11:24:59.307  4040  4040 D BuiltinUserCache: Added value for key statemachine/transition at time 1.481052299293E9
12-06 11:24:59.313  4040  4040 D TripDiaryStateMachineService: client is already connected, can directly handle the action
12-06 11:24:59.328  4040  4040 D TripDiaryStateMachineService: handleAction(local.state.waiting_for_trip_start, local.transition.stopped_moving) calle
12-06 11:24:59.342  4040  4040 D BuiltinUserCache: Added value for key background/battery at time 1.481052299332E9
12-06 11:24:59.349  4040  4040 D TripDiaryStateMachineService: TripDiaryStateMachineReceiver handleTripStart(local.transition.stopped_moving) called
12-06 11:24:59.354  4040  4040 D TripDiaryStateMachineService: Launched connect to the google API client, returning from onStartCommand
12-06 11:24:59.354  4040  4040 I chromium: [INFO:CONSOLE(25)] "About to show notification", source: file:///android_asset/www/js/incident/post-trip-prompt.js (25)

Not working case

Here's the not working case from the last run. Note no calls from the plugin.
Note that connecting to the google API client is slow and we restart the
service multiple times while doing so. Need to see who else is calling this,
but that is not important now.

12-06 11:28:22.193  2543  2556 I ActivityManager: Start proc 22518:edu.berkeley.eecs.emission/u0a106 for broadcast edu.berkeley.eecs.emission/.cordova.tracker.location.TripDiaryStateMachineReceiver
12-06 11:28:22.426 22518 22518 I TripDiaryStateMachineReceiver: noarg constructor called
12-06 11:28:22.455 22518 22518 I TripDiaryStateMachineReceiver: TripDiaryStateMachineReciever onReceive(android.app.ReceiverRestrictedContext@373230a, Intent { act=local.transition.stopped_moving flg=0x10 cmp=edu.berkeley.eecs.emission/.cordova.tracker.location.TripDiaryStateMachineReceiver (has extras) }) called
12-06 11:28:22.459 22518 22518 I System.out: logger == null, lazily creating new logger

12-06 11:28:22.490 22518 22518 I TripDiaryStateMachineService: Service created. Initializing one-time variables!
12-06 11:28:22.511 22518 22518 D TripDiaryStateMachineService: service started with flags = 1 startId = 1 action = local.transition.stopped_moving
12-06 11:28:22.516 22518 22518 D TripDiaryStateMachineService: service restarted! need to check idempotency!
12-06 11:28:22.522 22518 22518 D TripDiaryStateMachineService: after reading from the prefs, the current state is local.state.waiting_for_trip_start
12-06 11:28:22.578 22518 22518 D TripDiaryStateMachineService: Launched connect to the google API client, returning from onStartCommand

12-06 11:28:22.582 22518 22518 D TripDiaryStateMachineService: service started with flags = 1 startId = 3 action = local.transition.stopped_moving
12-06 11:28:22.615 22518 22518 D TripDiaryStateMachineService: Launched connect to the google API client, returning from onStartCommand

12-06 11:28:22.624 22518 22518 D TripDiaryStateMachineService: service started with flags = 1 startId = 4 action = local.transition.stopped_moving
12-06 11:28:22.662 22518 22518 D TripDiaryStateMachineService: Launched connect to the google API client, returning from onStartCommand

12-06 11:28:22.670 22518 22518 D TripDiaryStateMachineService: service started with flags = 0 startId = 6 action = local.transition.stopped_moving
12-06 11:28:22.727 22518 22518 D TripDiaryStateMachineService: onConnected(null) called
12-06 11:28:22.735 22518 22518 D TripDiaryStateMachineService: handleAction(local.state.waiting_for_trip_start, local.transition.stopped_moving) calle
12-06 11:28:22.757 22518 22518 D BuiltinUserCache: Added value for key background/battery at time 1.481052502739E9
12-06 11:28:22.763 22518 22518 D TripDiaryStateMachineService: TripDiaryStateMachineReceiver handleTripStart(local.transition.stopped_moving) called

@shankari
Copy link
Contributor Author

shankari commented Dec 6, 2016

Experimenting with making the plugin a full broadcast receiver to see if that will help.
First, replacing the

LocalBroadcastManager.getInstance(super.webView.getContext()).registerReceiver(receiver,filter)

with

super.webView.getContext().registerReceiver(receiver, filter);

I suspect this will not work, because the receiver will be killed, but let's see :)

@shankari
Copy link
Contributor Author

shankari commented Dec 6, 2016

Ok, so that worked without killing. I changed the plugin to use the non-local receiver, and then I changed the TripDiaryStateMachine receiver to stop broadcasting to local broadcast receiver and we still got a notification.

@shankari
Copy link
Contributor Author

shankari commented Dec 6, 2016

As expected in https://github.com/e-mission/e-mission-phone/issues/191#issuecomment-265260569, CDVBroadcaster was not invoked.

Successful run

12-06 12:24:03.625  2021  2102 D CDVBroadcaster: registerReceiver called with webview = org.apache.cordova.CordovaWebViewImpl@7b9c4bdfilter
12-06 12:24:05.289  2021  2021 D CDVBroadcaster: Firing event onPageFinished
12-06 12:24:05.295  2021  2021 E CDVBroadcaster: userdata [file:///android_asset/www/index.html#/root/main/metrics] for event [onPageFinished] is not a valid json object!
12-06 12:24:54.618  2021  2021 D CDVBroadcaster: Received broadcast in native code
12-06 12:24:54.624  2021  2021 D CDVBroadcaster: Firing event local.transition.stopped_moving
12-06 12:24:54.631  2021  2021 D CDVBroadcaster: Sending javascript to webView org.apache.cordova.CordovaWebViewImpl@7b9c4bd

Killed, last instance of CDVBroadcaster

12-06 12:28:18.231  2021  2021 D CDVBroadcaster: onDestroy called with = org.apache.cordova.CordovaWebViewImpl@7b9c4bdfilter

$ zgrep CDVBroadcaster /tmp/android_localbroadcast_tests_plugin_global_broadcast.log.gz | tail -n 1
12-06 12:28:18.231  2021  2021 D CDVBroadcaster: onDestroy called with = org.apache.cordova.CordovaWebViewImpl@7b9c4bdfilter

Not relaunched

12-06 12:28:54.821  3887  3887 I TripDiaryStateMachineReceiver: noarg constructor called
12-06 12:28:54.824  3887  3887 I System.out: logger == null, lazily creating new logger
12-06 12:28:54.848  3887  3887 I TripDiaryStateMachineReceiver: TripDiaryStateMachineReciever onReceive(android.app.ReceiverRestrictedContext@373230a, Intent { act=local.transition.stopped_moving flg=0x10 cmp=edu.berkeley.eecs.emission/.cordova.tracker.location.TripDiaryStateMachineReceiver (has extras) }) called
12-06 12:28:54.852  3887  3887 I System.out: logger == null, lazily creating new logger

@shankari
Copy link
Contributor Author

shankari commented Dec 6, 2016

I then tried to add an entry for this in the AndroidManifest.xml, similar to the existing TripDiaryStateMachineReceiver. Unfortunately, this is not possible because:

  • the class in the AndroidManifest.xml has to be a subclass of BroadcastReceiver
  • because we want to sent javascript back, it also has to be a subclass of CordovaPlugin
  • java doesn't support multiple inheritance

the plugin currently uses an anonymized inner class. last check to see whether a non-anonymized inner class will work.

@shankari
Copy link
Contributor Author

shankari commented Dec 6, 2016

last check to see whether a non-anonymized inner class will work.

public, non-anonymized inner class compiles.

public class CDVBroadcastReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, final Intent intent) {
        ...
    }
}
    <receiver android:enabled="true" android:name="org.bsc.cordova.CDVBroadcaster$CDVBroadcastReceiver">
        <intent-filter>
            <action android:name="local.transition.initialize" />
            <action android:name="local.transition.exited_geofence" />
            <action android:name="local.transition.stopped_moving" />
            <action android:name="local.transition.stop_tracking" />
            <action android:name="local.transition.start_tracking" />
        </intent-filter>
    </receiver>

@shankari
Copy link
Contributor Author

shankari commented Dec 6, 2016

public, non-anonymized inner class compiles.

but doesn't run. It fails with exception

12-06 13:09:43.573 17192 17192 E AndroidRuntime: java.lang.RuntimeException: Unable to instantiate receiver org.bsc.cordova.CDVBroadcaster$CDVBroadcastReceiver: java.lang.InstantiationException: java.lang.Class<org.bsc.cordova.CDVBroadcaster$CDVBroadcastReceiver> has no zero argument constructor
12-06 13:09:43.573 17192 17192 E AndroidRuntime: 	at android.app.ActivityThread.handleReceiver(ActivityThread.java:2706)
12-06 13:09:43.573 17192 17192 E AndroidRuntime: 	at android.app.ActivityThread.-wrap14(ActivityThread.java)
12-06 13:09:43.573 17192 17192 E AndroidRuntime: 	at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1421)
12-06 13:09:43.573 17192 17192 E AndroidRuntime: 	at android.os.Handler.dispatchMessage(Handler.java:102)
12-06 13:09:43.573 17192 17192 E AndroidRuntime: 	at android.os.Looper.loop(Looper.java:148)
12-06 13:09:43.573 17192 17192 E AndroidRuntime: 	at android.app.ActivityThread.main(ActivityThread.java:5417)
12-06 13:09:43.573 17192 17192 E AndroidRuntime: 	at java.lang.reflect.Method.invoke(Native Method)
12-06 13:09:43.573 17192 17192 E AndroidRuntime: 	at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726)
12-06 13:09:43.573 17192 17192 E AndroidRuntime: 	at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)

In spite of adding a noarg constructor

        public CDVBroadcastReceiver() {
            super();
            android.util.Log.d(TAG, "CDVBroadcastReceiver created");
            android.util.Log.d(TAG, "cordova activity = "+cordova.getActivity());
        }

I think that the problem is that as an inner class, it needs to be instantiated using an instance of the outer class instead of being instantiated directly.

@shankari
Copy link
Contributor Author

shankari commented Dec 6, 2016

Yup. from the android source code at https://github.com/android/platform_frameworks_base/blob/master/core/java/android/app/ActivityThread.java, we see that the class needs to be instantiated using newInstance.

        BroadcastReceiver receiver;
        try {
            java.lang.ClassLoader cl = packageInfo.getClassLoader();
            data.intent.setExtrasClassLoader(cl);
            data.intent.prepareToEnterProcess();
            data.setExtrasClassLoader(cl);
            receiver = (BroadcastReceiver)cl.loadClass(component).newInstance();
        } catch (Exception e) {
            if (DEBUG_BROADCAST) Slog.i(TAG,
                    "Finishing failed broadcast to " + data.intent.getComponent());
            data.sendFinished(mgr);
            throw new RuntimeException(
                "Unable to instantiate receiver " + component
                + ": " + e.toString(), e);
        }

@shankari
Copy link
Contributor Author

shankari commented Dec 6, 2016

Hm, but calling new on an inner class seems to work just fine.

    public class ApplicationTestInner extends BroadcastReceiver {
        public ApplicationTestInner() {
            System.out.println("no-arg constructor invoked");
        }

        @Override
        public void onReceive(Context context, Intent intent) {

        }
    }
            Object instantiated = new ApplicationTest.ApplicationTestInner();
            System.out.println("Successfully instantiated "+instantiated);
12-06 13:50:13.332    6187-6204/edu.berkeley.eecs.testfsmbroadcast I/TestRunner﹕ started: testBroadcastReceiverSyntax(edu.berkeley.eecs.testfsmbroadcast.ApplicationTest)
12-06 13:50:16.210    6187-6204/edu.berkeley.eecs.testfsmbroadcast I/System.out﹕ no-arg constructor invoked
12-06 13:50:19.652    6187-6204/edu.berkeley.eecs.testfsmbroadcast I/System.out﹕ Successfully instantiated edu.berkeley.eecs.testfsmbroadcast.ApplicationTest$ApplicationTestInner@6fc0ff

@shankari
Copy link
Contributor Author

shankari commented Dec 6, 2016

This is a known issue apparently, with instantiating inner classes from the AndroidManifest.xml
http://stackoverflow.com/questions/29947038/java-lang-instantiationexception-class-has-no-zero-argument-constructor#35231722

@shankari
Copy link
Contributor Author

shankari commented Dec 6, 2016

The workaround is to use a static class.

I can confirm that using a static class works.

12-06 14:17:21.809 21983 21983 D CDVBroadcaster: In static broadcast receiver no-arg constructor
    static public class CDVBroadcastReceiver extends BroadcastReceiver {
        public CDVBroadcastReceiver() {
            android.util.Log.d(TAG, "In static broadcast receiver no-arg constructor");
        }
        @Override
        public void onReceive(Context context, final Intent intent) {
        }
    }

But then we can't access any of the cordova stuff inside the inner class because it is not static in the outer.

@shankari
Copy link
Contributor Author

shankari commented Dec 6, 2016

I think we need to have the following structure:

  • new trip notify plugin
  • javascript configures with ('transition', configuration)
  • native code broadcast receiver
  • on receiving the appropriate transition, call the native interface to display local notification
  • rest of it works as usual???

Question:
Should we specify the full set of transitions, or should we have a unified, simplified set? I argue for unified, simplified set, since any users of the plugin won't care about the various stages in the iOS state diagram.

Second question:
What should the simplified set of transitions be?

  • trip start
  • trip end
  • stop tracking
  • start tracking
    (soon) - data synced

Let's start with this and add more as needed :)

@shankari
Copy link
Contributor Author

One interesting thing to note is that on android, the mapping from the local notification to the javascript callback happens as follows:

  • local notification is clicked
  • ClickActivity is launched and launches the real app in turn from its onCreate.
  • It then calls onClick, which fires the action or click events
  • and it does so by calling the sendJavascript method on the webview

When I see what happened to the ClickActivity, I see the following logs

12-11 12:34:17.008  2398  3644 I ActivityManager: START u0 {flg=0x40000000 cmp=edu.berkeley.eecs.emission/de.appplant.cordova.plugin.localnotification.ClickActivity (has extras)} from uid 10114 on display 0
12-11 12:34:17.008  2398  3644 W ActivityManager: startActivity called from non-Activity context; forcing Intent.FLAG_ACTIVITY_NEW_TASK for: Intent { flg=0x40000000 cmp=edu.berkeley.eecs.emission/de.appplant.cordova.plugin.localnotification.ClickActivity (has extras) }

12-11 12:34:17.072  2398  2410 I ActivityManager: START u0 {act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x30020000 pkg=edu.berkeley.eecs.emission cmp=edu.berkeley.eecs.emission/.MainActivity} from uid 10114 on display 0
12-11 12:34:17.077  4651  4651 W Activity: An activity without a UI must call finish() before onResume() completes

Can we do something similar to call javascript from the background in android, and make it easier to compose plugins? Need to investigate further.

@shankari
Copy link
Contributor Author

shankari commented Dec 12, 2016

Also, swiping away the notification without clicking on anything leads to a "cancel" event. I have added a callback for it as well (#a858446ed8eabadc2e69a424bcb85a3bbccbcb4f)

@shankari
Copy link
Contributor Author

Usercache changes to support local storage:
e-mission/cordova-usercache#31

shankari referenced this issue in shankari/e-mission-transition-notify Dec 12, 2016
Now that we are passing in the correct notification with a real category and
actions, we no longer need to forcibly set them.
https://github.com/e-mission/e-mission-phone/issues/191#issuecomment-265659578
@shankari
Copy link
Contributor Author

@shankari
Copy link
Contributor Author

Some more testing indicates that there are subtle changes between iOS 9 and iOS 10.

iOS 9.2: scheduleLocalNotification works.

/**
 * Schedule the local notification.
 */
- (void) scheduleLocalNotification:(UILocalNotification*)notification
{
    [self cancelForerunnerLocalNotification:notification];
    [self.app scheduleLocalNotification:notification];
}

iOS 10.1: scheduleLocalNotification does NOT work. presentLocalNotificationNow does work.

iOS 10 working iOS 9 working
simulator screen shot dec 11 2016 8 30 47 pm simulator screen shot dec 11 2016 8 27 11 pm

I wonder if this is because of this change:
notclive/cordova-plugin-local-notifications@fb34f81

@shankari
Copy link
Contributor Author

shankari commented Dec 12, 2016

I wonder if this is because of this change:

Yes. Fixed with an easier commit
EddyVerbruggen/cordova-plugin-local-notifications@5dad35f

Manually ported the fix as
shankari/cordova-plugin-local-notifications@a536d6d

@shankari
Copy link
Contributor Author

There also appear to be minor differences between iPhone 7 and iPhone 6 for the same OS version (10.1). This is really bizarre. Does the OS really check which hardware it is running on for something as simple as showing notifications? Here are screenshots from various emulators, run without changing the code in any way. It looks like on iPhone 7, actions are not displayed correctly ( at least according to the emulator).

iPhone 7 Plus iPhone 7
simulator screen shot dec 11 2016 9 16 24 pm simulator screen shot dec 11 2016 9 14 46 pm
iPhone 6 Plus iPhone 6 Plus "view"
simulator screen shot dec 11 2016 9 13 15 pm simulator screen shot dec 11 2016 9 13 10 pm
iPhone 6 iPhone 6 "view"
simulator screen shot dec 11 2016 9 09 15 pm simulator screen shot dec 11 2016 9 09 10 pm

@shankari
Copy link
Contributor Author

shankari commented Dec 12, 2016

This appears to be a regression in registerUserNotificationSettings. As we can see from the screenshot below, the settings passed in to the function had _actionsByContext: 1 -> [array with 2 elements]. But when the settings are retrieved after the call, they have _actionsByContext: 0 -> [array with 0 elements].

Have filed a customer support issue with Apple.

iphone_7_settings_not_updated_correctly

@shankari
Copy link
Contributor Author

Changes to help debug this issue

--- src/ios/UIApplication+APPLocalNotification.m        2016-12-02 21:21:00.000000000 -0800
+++ /Users/shankari/e-mission/e-mission-phone/platforms/ios/emission/Plugins/de.appplant.cordova.plugin.local-notification-ios9-fix/UIApplication+APPLocalNotification.m      2016-12-11 22:11:57.000000000 -0800
@@ -77,6 +77,11 @@
 
         [[UIApplication sharedApplication]
          registerUserNotificationSettings:settings];
+        
+        UIUserNotificationSettings *afterModSettings = [[UIApplication sharedApplication]
+                    currentUserNotificationSettings];
+        NSLog(@"Random log statement to let me see what the afterModSettings are");
+        
     }
 }
 
@@ -149,7 +154,7 @@
                         } else {
                             [newCategory setActions:[[actionsArray reverseObjectEnumerator] allObjects] forContext:UIUserNotificationActionContextMinimal];
                         }
-                        [newCategory setActions:actionsArray forContext:UIUserNotificationActionContextDefault];
+//                        [newCategory setActions:actionsArray forContext:UIUserNotificationActionContextDefault];
                         [allNotificationCategories setObject:newCategory forKey: category];
                     }
                 }

@shankari
Copy link
Contributor Author

shankari commented Dec 12, 2016

Broken on iPhone 6s as well. Basically all the phones that only run iOS 10.
Confirmed that it works on iPhone 6 Plus and not on iPhone 6s.

@shankari
Copy link
Contributor Author

shankari commented Dec 13, 2016

Quickly checking how this https://github.com/e-mission/e-mission-phone/issues/191#issuecomment-266335713 works to see if we can try to do sth crossplatform anyway.

Start with a killed app

Check 1:

  • cancel notification
    • login, app is not running
    • check history, app is not running
    • check debugger, I see a process. attach to it
    • launch app manually, cancel callback
      • deviceready
      • send queued javascript
      • bunch of alerts

Check 2:

  • kill app again
    • nothing to connect to

    • generate notification

    • now there is a process, but we don't know how it got started

    • attach to it

    • cancel notification

    • got sendJavascript callback

      • backtrace
        • sendJavascript
        • fireEvent
        • AbstractClearReceiver
    • process got killed as I was typing all this out

    • because of timeout

      12-13 11:09:30.313  2411  2438 W BroadcastQueue: Timeout of broadcast BroadcastRecord{5e0
      8158 u0 737678} - receiver=android.os.BinderProxy@a1012b1, started 60003ms ago
      12-13 11:09:30.313  2411  2438 W BroadcastQueue: Receiver during timeout: ResolveInfo{470
      1396 edu.berkeley.eecs.emission/de.appplant.cordova.plugin.localnotification.ClearReceive
      r m=0x0}
      ...
      12-13 11:09:34.076  2411  2438 E ActivityManager: ANR in edu.berkeley.eecs.emission
      12-13 11:09:34.076  2411  2438 E ActivityManager: PID: 6170
      12-13 11:09:34.076  2411  2438 E ActivityManager: Reason: Broadcast of Intent { act=737678 flg=0x10 cmp=edu.berkeley.eecs.emission/de.appplant.cordova.plugin.localnotification.ClearReceiver (has extras) }
      

      Let's retry and be quicker

      12-13 11:34:31.160  2411  4736 I ActivityManager: Force stopping edu.berkeley.eecs.testst
      atemachinetransitions appid=10115 user=0: finished inst
      12-13 11:34:31.161  2411  4736 I ActivityManager: Killing 12048:edu.berkeley.eecs.teststa
      temachinetransitions/u0a115 (adj 0): stop edu.berkeley.eecs.teststatemachinetransitions
      12-13 11:34:31.161  2411  2438 W ActivityManager: Error shutting down UiAutomationConnection
      12-13 11:34:31.240  2411  4738 W ActivityManager: Spurious death for ProcessRecord{c993777 0:edu.berkeley.eecs.teststatemachinetransitions/u0a115}, curProc for 12048: null
      
      ....
      
      Nothing else
      

It looks like basically there is a difference between activities and processes.
The e-mission process generally stays around in the background even if there is no activity, and is launched even by the broadcaster, etc. It is primarily killed when you force kill.

@shankari
Copy link
Contributor Author

Note that when we cancel, we use the CancelBroadcaster, but when we click, the ClickActivity is launched. Let us see:

  • when it is created
  • whether it is visible in the chooser
  • when it is deleted

Start:

  • app killed, no process, no activity

Broadcast event

  • nothing displayed, no activity in list, process available to attach
  • attach process

Click on event

  • onCreate called

    • nothing displayed in UI, actvity in list
    • sendJavascript, device is not ready, queued
    • actual emission launched
    • javascript callbacks recovered
  • onStop and onDestroy appear to never be called

  • activity in list is only emission

Let's try again without using the debugger

@shankari
Copy link
Contributor Author

At any rate, it seems possible to launch an activity directly into the background, the way the ClickActivity is launched. Should finish experimenting with that. Should I do that now, or just get this to work for now and do it later?

Should not be too hard to get it to work for now using native code.

@shankari
Copy link
Contributor Author

Let us first understand this better and then do it in native code for now.
After we get the push notifications from the server to work, can focus on making this extensible.

@shankari
Copy link
Contributor Author

Note that when we cancel, we use the CancelBroadcaster, but when we click, the ClickActivity is launched. Let us see:

  • when it is created
  • whether it is visible in the chooser
  • when it is deleted

Start:

  • app killed, no process, no activity

Broadcast event

  • nothing displayed, no activity in list, process available to attach
  • attach process

Click on event

  • onCreate called

    • nothing displayed in UI, actvity in list
    • sendJavascript, device is not ready, queued
    • actual emission launched
    • javascript callbacks recovered
  • onStop and onDestroy appear to never be called

  • activity in list is only emission

Let's try again without using the debugger

@shankari
Copy link
Contributor Author

onStop and onDestroy appear to never be called

This is just because I hadn't updated the app. After re-installing, we were
able to get the messages. It looks like the ClickActivity is killed fairly
quickly (within a second).

That makes it trickier to use it to run javascript in a crossplatform way. In
particular, cordova is STILL loading when it is killed. Maybe it will be a bit
better if we have a separate app with fewer plugins...

12-13 13:25:04.669 20212 20212 D AbstractClickActivity: About to call onCreate
12-13 13:25:04.683 20212 20212 D AbstractClickActivity: launching app from click activity
12-13 13:25:04.685  2288  4675 I ActivityManager: START u0 {act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x30020000 pkg=edu.berkeley.eecs.emission cmp=edu.berkeley.eecs.emission/.MainActivity} from uid 10114 on display 0
12-13 13:25:04.700 20212 20212 W Activity: An activity without a UI must call finish() before onResume() completes
12-13 13:25:04.815 20212 20212 I System.out: During plugin initialize, created usercacheedu.berkeley.eecs.emission.cordova.usercache.BuiltinUserCache@17a2df5
12-13 13:25:05.483 20212 20212 D AbstractClickActivity: About to call onStop
12-13 13:25:05.483 20212 20212 D AbstractClickActivity: About to call onDestroy
...
12-13 13:25:06.999 20212 20212 I chromium: [INFO:CONSOLE(2)] "Ionic Core:", source: file:///android_asset/www/lib/ionic-platform-web-client/dist/ionic.io.bundle.min.js (2)
12-13 13:25:07.080 20212 20212 D JsMessageQueue: Set native->JS mode to OnlineEventsBridgeMode
12-13 13:25:09.653 20212 20212 I chromium: [INFO:CONSOLE(32)] "Starting config", source: file:///android_asset/www/js/app.js (32)

@shankari
Copy link
Contributor Author

Let's see how long it sticks around if it doesn't launch another app.
Commenting out the launchApp from ClickActivity...
receive_clear_no_main_launch.log.gz

Answer is: a little longer (~ 1 minute)

12-13 13:50:38.779 27991 27991 D AbstractClickActivity: About to call onCreate
12-13 13:50:38.794 27991 27991 D ClickActivity: Skipping launch of app
12-13 13:50:38.795 27991 27991 W Activity: An activity without a UI must call finish() before onResume() completes
12-13 13:50:41.244  2288  2301 I ActivityManager: Waited long enough for: ServiceRecord{1b0f562 u0 edu.berkeley.eecs.emission/.cordova.tracker.location.TripDiaryStateMachineService}
12-13 13:50:51.690 27991 27991 D AbstractClickActivity: About to call onStop
12-13 13:50:51.692 27991 27991 D AbstractClickActivity: About to call onDestroy


12-13 13:51:27.213 27991 27991 D AbstractClickActivity: About to call onCreate
12-13 13:51:27.234 27991 27991 D ClickActivity: Skipping launch of app
12-13 13:51:27.235 27991 27991 W Activity: An activity without a UI must call finish() before onResume() completes
12-13 13:53:27.714 27991 27991 D AbstractClickActivity: About to call onStop

@shankari
Copy link
Contributor Author

shankari commented Dec 13, 2016

Only two launches detected. Dunno which two.

Nothing running. Click yes. Then launch gmail.
(7 secs from launch, 1 sec from gmail launch)
receive_clear_13th_3_30_pm.log.gz

12-13 15:21:56.859 27991 27991 D AbstractClickActivity: About to call onCreate
12-13 15:21:56.876 27991 27991 D ClickActivity: Skipping launch of app
12-13 15:21:56.877 27991 27991 W Activity: An activity without a UI must call finish() before onResume() completes
12-13 15:22:02.673  2288  4369 I ActivityManager: START u0 {act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10200000 cmp=com.google.android.gm/.ConversationListActivityGmail (has extras)} from uid 10034 on display 0
12-13 15:22:03.170 27991 27991 D AbstractClickActivity: About to call onStop
12-13 15:22:03.170 27991 27991 D AbstractClickActivity: About to call onDestroy

Gmail running. Click yes.
(5 secs from launch)

12-13 15:23:47.235 27991 27991 D AbstractClickActivity: About to call onCreate
12-13 15:23:47.253 27991 27991 D ClickActivity: Skipping launch of app
12-13 15:23:47.255 27991 27991 W Activity: An activity without a UI must call finish() before onResume() completes
12-13 15:23:52.901 27991 27991 D AbstractClickActivity: About to call onStop
12-13 15:23:52.902 27991 27991 D AbstractClickActivity: About to call onDestroy

Gmail running. Click yes. Click home button.

@shankari
Copy link
Contributor Author

Now, launching the MainActivity directly instead of a ClickActivity

    @Override
    public Notification buildNotification (Builder builder) {
        return builder
                .setTriggerReceiver(TriggerReceiver.class)
                .setClearReceiver(ClearReceiver.class)
                .setClickActivity(MainActivity.class)
                .build();
    }

@shankari
Copy link
Contributor Author

Launching the MainActivity instead of ClickActivity causes the screen to be displayed, probably because it has a UI. It looks like activities are only opened in the background if they have no UI. But without a UI, I can't run javascript. So this idea is dead in the water.

@shankari
Copy link
Contributor Author

shankari commented Dec 14, 2016

If we want to test this again, we need to change both instances of setClickActivity - in RestoreReceiver and TriggerReceiver

@shankari
Copy link
Contributor Author

So I made changes directly to the native code to fill in start_ts, end_ts, start_lat, start_lng, end_lat, end_lng. The data is available from the data field of the notification, consistent with the documentation at
https://github.com/shankari/cordova-plugin-local-notifications#sample-2

On android, the data is returned as JSON to javascript.
But on iOS, it is returned as a string.

Printing description of currNotifyString:
{"category":"REPORT_INCIDENT",
"data":{"start_ts":1481824746.492381,
    "end_ts":1481825085.772217,
    "end_lat":37.33468543,
    "end_lng":-122.04152666},
"id":737678,
"title":"Trip just ended",
"text":"Incident to report?",
"autoClear":true,
"actions":[{"destructive":false,
    "activationMode":"foreground",
    "title":"Mute",
    "icon":"res:\/\/ic_moreoptions",
    "identifier":"MUTE",
    "authenticationRequired":false},
    {"destructive":false,
    "activationMode":"foreground",
    "title":"Yes",
    "icon":"res:\/\/ic_signin",
    "identifier":"REPORT",
    "authenticationRequired":false}]}

Note how the data is a JSON object when the notification is posted but is a string on return.

Received local notification <UIConcreteLocalNotification: 0x61800019cb10>{fire date = Thursday, December 15, 2016 at 10:05:50 AM Pacific Standard Time, time zone = America/Los_Angeles (PST) offset -28800, repeat interval = 0, repeat count = UILocalNotificationInfiniteRepeatCount, next fire date = (null), user info = {
    actions =     (
                {
            activationMode = foreground;
            authenticationRequired = 0;
            destructive = 0;
            icon = "res://ic_moreoptions";
            identifier = MUTE;
            title = Mute;
        },
                {
            activationMode = foreground;
            authenticationRequired = 0;
            destructive = 0;
            icon = "res://ic_signin";
            identifier = REPORT;
            title = Yes;
        }
    );
    at = 1481825135;
    badge = 0;
    category = "REPORT_INCIDENT";
    data = "{\"start_ts\":1481824746.492381,\"end_ts\":1481825085.772217,\"end_lat\":37.33468543,\"end_lng\":-122.04152666}";
    id = 737678;
    sound = "res://platform_default";
    text = "Incident to report?";
    title = "Trip just ended";
}}

It should be possible to reconstruct a JSON object from the string before
returning it, and that is The Right Thing to do, but:

  • I am not sure why this isn't happening already. It would be good to figure
    that out and do it the Right Way.
  • It looks like in the case of responseText, the data is overridden in
    handleNotificationAction so there might be other bugs too.
     NSDictionary* responseInfo = [userInfo objectForKey:@"responseInfo"];
     
     NSDictionary* dataDict = [NSDictionary dictionaryWithObjectsAndKeys:identifier, @"identifier",
                               [responseInfo objectForKey:@"UIUserNotificationActionResponseTypedTextKey"], @"responseInfoText", nil];
     NSData* jsonData = [NSJSONSerialization dataWithJSONObject:dataDict options:0 error:nil];
     NSString* data = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];

@shankari
Copy link
Contributor Author

This should never happen in the real world, but it sometimes happens in testing
that because of our breakpoints, and the continuous looping of the location,
the geofence has exited and the trip restarted by the time we exit this
handler. We then push data to the server, which deletes the start transition.
If we don't handle that here, we get an ASSERTion failure and the FSM dies
there so we never start a trip again. Although this is rare, we want to handle
this use case gracefully so we just get the oldest point in the database.

@shankari
Copy link
Contributor Author

@shankari
Copy link
Contributor Author

shankari commented Feb 4, 2017

version 1.7.0 added this functionality and version 1.8.0 fixed some minor issues with it on iOS, notably prompting while in the foreground on android.

concretely, native code plugin is at:
https://github.com/e-mission/e-mission-transition-notify/

javascript changes are from
e-mission/e-mission-phone@ee40306
to
e-mission/e-mission-phone@13d7bc9

@shankari
Copy link
Contributor Author

shankari commented Feb 4, 2017

closing the issue!

@shankari shankari closed this as completed Feb 4, 2017
@shankari shankari transferred this issue from e-mission/e-mission-phone Feb 11, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant