Closed shankari closed 2 years ago
Re-checking the permissions.. It looks like we need to get the foreground and background location permissions separately. https://developer.android.com/about/versions/11/privacy/location#request-background-location-separately
We seem to currently be getting them both at the same time, and we are targeting API 30 in this beta version. https://github.com/e-mission/e-mission-data-collection/blob/58ae59add95097dbfdd1f1a9ef2bbcd8f6ec711b/src/android/verification/SensorControlForegroundDelegate.java#L87 Why didn't we run into this?
Checking against the CEO ebike branch since it still has the old UI...
The targetSDK version is 30 (Android 11)
<?xml version='1.0' encoding='utf-8'?>
<manifest android:hardwareAccelerated="true" android:versionCode="16" android:versionName="1.2.1" package="gov.colorado.energyoffice.emission" xmlns:android="http://schemas.android.com/apk/res/android">
<uses-sdk
android:minSdkVersion="22"
android:targetSdkVersion="30" />
we call both permissions
2021-10-27 15:41:54.544 21277-21501/gov.colorado.energyoffice.emission I/SensorPermissionsAndSettingsForegroundDelegate: Both permissions missing, requesting both
if(!cordova.hasPermission(SensorControlConstants.LOCATION_PERMISSION) &&
(Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) &&
!cordova.hasPermission(SensorControlConstants.BACKGROUND_LOC_PERMISSION)) {
Log.i(cordova.getActivity(), TAG, "Both permissions missing, requesting both");
cordova.requestPermissions(plugin, SensorControlConstants.ENABLE_BOTH_PERMISSION,
new String[]{SensorControlConstants.LOCATION_PERMISSION, SensorControlConstants.BACKGROUND_LOC_PERMISSION});
return;
}
And we get both permissions.
2021-10-27 15:41:55.962 21277-21277/gov.colorado.energyoffice.emission I/SensorPermissionsAndSettingsForegroundDelegate: onRequestPermissionResult called with 362253740
2021-10-27 15:41:55.976 21277-21277/gov.colorado.energyoffice.emission I/SensorPermissionsAndSettingsForegroundDelegate: permissions are [android.permission.ACCESS_FINE_LOCATION, android.permission.ACCESS_BACKGROUND_LOCATION]
2021-10-27 15:41:55.992 21277-21277/gov.colorado.energyoffice.emission I/SensorPermissionsAndSettingsForegroundDelegate: grantResults are [0, 0]
2021-10-27 15:41:56.509 21277-21277/gov.colorado.energyoffice.emission D/SensorControlBackgroundChecker: checkSelfPermission returned 0
2021-10-27 15:41:56.521 21277-21277/gov.colorado.energyoffice.emission D/SensorControlBackgroundChecker: checkLocationPermissions returned true, checking background permission
2021-10-27 15:41:56.532 21277-21277/gov.colorado.energyoffice.emission D/SensorControlBackgroundChecker: checkSelfPermission returned 0
2021-10-27 15:41:56.542 21277-21277/gov.colorado.energyoffice.emission D/SensorControlBackgroundChecker: checkBackgroundLocPermissions returned true, checking location settings
Although cordova just passes the code through to the activity anyway with all the permissions requested at the same time. https://github.com/e-mission/e-mission-docs/issues/680#issuecomment-953383000
Double checked the targetSDK version by using
Log.i(cordova.getActivity(), TAG,
"Target SDK is "+cordova.getActivity().getApplicationContext().getApplicationInfo().targetSdkVersion);
We are at target API 30, and it still works. Maybe this only applies for phones running API 30 and above. But at that point, we launch the app settings page anyway, so not sure that it matters.
2021-10-27 16:39:15.730 22678-22934/gov.colorado.energyoffice.emission I/SensorPermissionsAndSettingsForegroundDelegate: Target SDK is 30
2021-10-27 16:39:16.357 22678-22934/gov.colorado.energyoffice.emission I/SensorPermissionsAndSettingsForegroundDelegate: Both permissions missing, requesting both
2021-10-27 16:39:36.776 22678-22934/gov.colorado.energyoffice.emission 2021-10-27 16:39:53.194 22678-22678/gov.colorado.energyoffice.emission I/SensorPermissionsAndSettingsForegroundDelegate: onRequestPermissionResult called with 362253740
2021-10-27 16:39:53.209 22678-22678/gov.colorado.energyoffice.emission I/SensorPermissionsAndSettingsForegroundDelegate: permissions are [android.permission.ACCESS_FINE_LOCATION, android.permission.ACCESS_BACKGROUND_LOCATION]
2021-10-27 16:39:53.226 22678-22678/gov.colorado.energyoffice.emission I/SensorPermissionsAndSettingsForegroundDelegate: grantResults are [0, 0]
2021-10-27 16:39:53.883 22678-22678/gov.colorado.energyoffice.emission D/SensorControlBackgroundChecker: checkSelfPermission returned 0
2021-10-27 16:39:53.922 22678-22678/gov.colorado.energyoffice.emission D/SensorControlBackgroundChecker: checkLocationPermissions returned true, checking background permission
2021-10-27 16:39:53.938 22678-22678/gov.colorado.energyoffice.emission D/SensorControlBackgroundChecker: checkSelfPermission returned 0
2021-10-27 16:39:53.952 22678-22678/gov.colorado.energyoffice.emission D/SensorControlBackgroundChecker: checkBackgroundLocPermissions returned true, checking location settings
It is a bit tricky to figure how how best to merge the background and foreground calls in this case.
The background calls generate different notifications depending on which set of permissions was missing, and clicking the notifications causes different permissions to be requested.
But for the plugin calls, we want to just figure out whether or not the permissions have been granted, at least for check. And "fix" is completely different.
If we could unify the background calls to generate the same notification, we could unify the calls. But then we would not know which permissions are missing. But we might go to the model in which all the background issues basically just go to the status screen. In that case, the unification would be possible. Let's wait and see how the code evolves.
Hi @shankari . Ok good Luck on openpath. Non we don't use openid currently.
Le mer. 27 oct. 2021 à 22:27, shankari @.***> a écrit :
@PatGendre https://github.com/PatGendre that was with upgrading everything other than the OpenPATH plugins. I am now working on updating the OpenPATH plugins.
- for the auth: I don't know whether you are still using OpenID authentication. If so, I would appreciate help testing e-mission/cordova-jwt-auth#45 https://github.com/e-mission/cordova-jwt-auth/pull/45
- for the data collection: I've been noodling with e-mission/e-mission-phone#804 (comment) https://github.com/e-mission/e-mission-phone/pull/804#issuecomment-953283372 😄
— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/e-mission/e-mission-docs/issues/680#issuecomment-953283995, or unsubscribe https://github.com/notifications/unsubscribe-auth/ABGOKV3R7GWZR477Y6YO5P3UJBOEFANCNFSM5GEDGAAQ . Triage notifications on the go with GitHub Mobile for iOS https://apps.apple.com/app/apple-store/id1477376905?ct=notification-email&mt=8&pt=524675 or Android https://play.google.com/store/apps/details?id=com.github.android&referrer=utm_campaign%3Dnotification-email%26utm_medium%3Demail%26utm_source%3Dgithub.
Moving on to notification checks... This has a pretty simple check: https://developer.android.com/reference/android/app/NotificationManager#areNotificationsEnabled()
"Returns whether notifications from the calling package are blocked."
There is also a similar check: https://developer.android.com/reference/android/app/NotificationManager#areNotificationsPaused()
The documentation for this is: "Returns whether notifications from this package are temporarily hidden. This could be done because the package was marked as distracting to the user via PackageManager#setDistractingPackageRestrictions(String[], int) or because the package is PackageManager#setPackagesSuspended(String[], boolean, PersistableBundle, PersistableBundle, SuspendDialogInfo) suspended."
However, I can't find setDistractingPackageRestrictions
or setPackagesSuspended
in the PackageManager
There is a https://developer.android.com/reference/android/content/pm/PackageManager#isPackageSuspended().
Not sure how apps are suspended and how they should be unsuspended. Does this just mean the app lifecycle? Not sure. The only documentation I can find is about apps suspended from the play store, and this note in the release notes. https://developer.android.com/about/versions/pie/android-9.0-changes-28#suspended-apps
wrt non distraction, I do find https://developer.android.com/about/versions/10/behavior-changes-all?hl=de#app-usage
Per-app distraction state - Android 10 can selectively set apps to a "distraction state" where their notifications are suppressed and they do not appear as suggested apps.
The only match that I can find is related to avoiding distracted driving https://source.android.com/devices/automotive/driver_distraction/guidelines
@PatGendre @jf87 @asiripanich @ericafenyo I am thinking of dropping support for android 5.0 It is the only version that still supports static permissions, so dropping it will simplify the codebase somewhat. By targeting API 23 instead of 22, we go from 92% support to 85% support, which doesn't seem like a lot. Thoughts?
(screenshot from Android Studio)
Suspension does seem to be related to Google Play store suspension. I cannot find any other examples of it, and the the API signature PackageManager#setPackagesSuspended(String[], boolean, PersistableBundle, PersistableBundle, setPackagesSuspended)
seems to indicate that the user has other ways of knowing that the app has been suspended.
Finding the appropriate method in the Android Open Source codebase: https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/content/pm/PackageManager.java#7912
* Puts the package in a suspended state, where attempts at starting activities are denied.
* When the user tries to launch a suspended app, a system dialog with the given dialogMessage
It does not look like the user can fix this. The user can't even launch the activity to fix this.
The distracting packages are different.
* These packages can have certain restrictions set that should discourage the user to launch
them often. For example, notifications from such an app can be hidden, or the app can be removed from launcher suggestions, so the user is able to restrict their use of these apps.
Again, it is not clear how the user can clear these restrictions.
Ah I found it by searching the settings. It is part of the new "Digital Wellness" tools.
There does not seem to be a way to open that screen - there is no settings constant for now. https://developer.android.com/reference/android/provider/Settings
I think we should ignore the notification paused checks for now.
Given all this, I don't think we should add a section on "notifications paused" to the status screen. I am going to commit the change and then roll back the UI only changes. This allows us to re-enable the check with UI-only changes later if we need to.
Also storing the UI change as a patch here in case we need it later. paused_notification_check_support.patch.gz
Moving on to the background restrictions, we start with the auto-reset of permissions for rarely used apps (https://github.com/e-mission/e-mission-docs/issues/680#issuecomment-945047105).
There is a workaround which involves compat libraries from androidx (https://developer.android.com/topic/performance/app-hibernation#handle-hibernation-android10-lower).
However, the API is in the most recent version of androidX, which has a minCompileSDK of 31. cordova supports 30 by default, and doesn't have a way to override without also overridding the target SDK.
Filed https://github.com/apache/cordova-android/issues/1373
Trying to manually override to API 31 and see if it works.
Did not work, got error
> Task :app:compileDebugJavaWithJavac FAILED
An exception has occurred in the compiler (1.8.0_252). Please file a bug against the Java compiler via the Java bug reporting page (http://bugreport.java.com) after checking the Bug Database (http://bugs.java.com) for duplicates. Include your program and the following diagnostic in your report. Thank you.
java.lang.AssertionError: annotationType(): unrecognized Attribute name MODULE (class com.sun.tools.javac.util.UnsharedNameTable$NameImpl)
Downgraded to API 30, confirmed that it worked.
Removed all SDK, just in case that was the issue (to be consistent with https://github.com/invertase/react-native-firebase/issues/5685)
Re-ran with API 30.
Got logs
Checking the license for package Android SDK Platform 30 in /Users/kshankar/Library/Android/sdk/licenses
License for package Android SDK Platform 30 accepted.
Preparing "Install Android SDK Platform 30 (revision: 3)".
"Install Android SDK Platform 30 (revision: 3)" ready.
Installing Android SDK Platform 30 in /Users/kshankar/Library/Android/sdk/platforms/android-30
"Install Android SDK Platform 30 (revision: 3)" complete.
"Install Android SDK Platform 30 (revision: 3)" finished.
There is one SDK
$ ls ~/Library/Android/sdk/platforms/
android-30
Upgraded to compileSdk 31
. new SDK was downloaded but got the same error
Checking the license for package Android SDK Platform 31 in /Users/kshankar/Library/Android/sdk/licenses
License for package Android SDK Platform 31 accepted.
Preparing "Install Android SDK Platform 31 (revision: 1)".
"Install Android SDK Platform 31 (revision: 1)" ready.
Installing Android SDK Platform 31 in /Users/kshankar/Library/Android/sdk/platforms/android-31
"Install Android SDK Platform 31 (revision: 1)" complete.
"Install Android SDK Platform 31 (revision: 1)" finished.
An exception has occurred in the compiler (1.8.0_252). Please file a bug against the Java compiler via the Java bug reporting page (http://bugreport.java.com) after checking the Bug Database (http://bugs.java.com) for duplicates. Include your program and the following diagnostic in your report. Thank you.
java.lang.AssertionError: annotationType(): unrecognized Attribute name MODULE (class com.sun.tools.javac.util.UnsharedNameTable$NameImpl)
Upgrading to Java 11 as well compiles and runs. I had to rename Android Studio
to Android_Studio
to deal with path issues, but once I did that everything worked.
$ echo $JAVA_HOME
/Applications/Android_Studio.app/Contents/jre/Contents/Home
$ $JAVA_HOME/bin/java --version
openjdk 11.0.10 2021-01-19
OpenJDK Runtime Environment (build 11.0.10+0-b96-7281165)
OpenJDK 64-Bit Server VM (build 11.0.10+0-b96-7281165, mixed mode)
$ npx cordova build android
> Configure project :app
Adding classpath: com.google.gms:google-services:4.3.3
WARNING:: Configuration 'compile' is obsolete and has been replaced with 'implementation' and 'api'.
It will be removed in version 7.0 of the Android Gradle plugin.
For more information, see http://d.android.com/r/tools/update-dependency-configurations.html.
WARNING:: Using flatDir should be avoided because it doesn't support any meta-data formats.
> Task :CordovaLib:compileDebugJavaWithJavac
Note: Some input files use or override a deprecated API.
Note: Recompile with -Xlint:deprecation for details.
> Task :app:compileDebugJavaWithJavac
Note: Some input files use or override a deprecated API.
Note: Recompile with -Xlint:deprecation for details.
Note: Some input files use unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.
BUILD SUCCESSFUL in 22s
49 actionable tasks: 8 executed, 41 up-to-date
Built the following apk(s):
.../platforms/android/app/build/outputs/apk/debug/app-debug.apk
If we go down this route, we will need to use java 11 in the CI. On my laptop, I use the built-in java in Android Studio to get Java 11. However, it looks like the Github virtual environments only install the android SDK, not android studio https://github.com/actions/virtual-environments/blob/macOS-11/20211018.1/images/macos/macos-11-Readme.md
We would need to have separate setup scripts for CI and laptop that will set the JAVA_HOME differently. This sounds like a Bad Idea. Downloading JDK 11 from adoptjdk instead...
from
Seems reasonable to me to drop support for phones with an Android version released 7 years ago. ;)
@PatGendre @jf87 @asiripanich @ericafenyo I am thinking of dropping support for android 5.0 It is the only version that still supports static permissions, so dropping it will simplify the codebase somewhat. By targeting API 23 instead of 22, we go from 92% support to 85% support, which doesn't seem like a lot. Thoughts?
@shankari yes it is reasonable to drop support for Android 5 if it makes thé upgrade easier
Implemented the new checks for the permissions auto-reset.
Works fine on both android 30 (apps restricted, need to unrestrict), and android 29 (FEATURE_NOT_AVAILABLE
, apps unrestricted)
API | OpenPATH | Settings |
---|---|---|
30 | ||
29 |
Moving on to the final two tasks for android:
For the first one, there are at least two libraries that attempt to automate the process of launching the screen to fix the background settings.
(1) supports more devices and has more forks and stars, but it appears to focus only on "AutoStart" settings. I have not seen any issues with push notifications, even on the Huawei from #677, the push notifications are always displayed. What we do have issues with is background optimizations. But the settings screens for both seem to be the same - e.g.
From https://github.com/WanghongLin/CRomAppWhitelist/blob/master/cromappwhitelist/src/main/assets/default_appwhitelist.json, the huawei background optimization setting is "com.huawei.systemmanager.optimize.process.ProtectActivity"
, which is on the backup list for Huawei
Also, (2) only supports Chinese phones, so it doesn't support Samsung, which is the most common phone model that we have had issues with. In fact, it is the only model that we used to warn people about before. https://github.com/e-mission/e-mission-phone/blob/master/www/js/intro.js#L52
if($window.device.manufacturer.toLowerCase() == "samsung") {
$scope.backgroundRestricted = true;
$scope.allowBackgroundInstructions = $translate.instant("intro.allow_background.samsung");
}
So we have to go with the first option, or something similar.
But the first option also has the problem that "com.huawei.systemmanager.optimize.process.ProtectActivity"
is on the backup list for Huawei. So it may not be displayed if the first activity is available. But the first activity (StartupNormalAppListActivity
) is not on the list for (2), even for autostart. And the activity in (2) for autostart (BootStartActivity
) is not in the list for (1).
ARRGGHHH!
So it looks like we may want to try both libraries.
I also note that these may be more restrictive than we need. For example, the OnePlus is listed in (1), but we have reports from two OnePlus users that they don't have any issues. https://github.com/e-mission/e-mission-docs/issues/412#issue-453914743
So making users change settings unnecessarily during onboarding (or later!!) seems like a bad idea.
An alternate approach would be to use stats such as "number of periodic syncs" or "time delta during trip" to determine whether the app is working properly in the background. If it is not, then we ask the user to try various settings.
It would be great if we could record the finally selected settings, but in the absence of callbacks, I don't see an automated way to do that. Not sure if we can ask users to manually record that. We can certainly record whether or not changes were needed for a particular manufacturer/OS combination.
So this argues for the following design:
This seems super complicated, and potentially requires a new POST API or a new user_input
or ???
Let's implement the second task on android and finish the implementation on iOS before tackling the first task.
One other issue on android: what if the user denies the permission?
I noticed this behavior while testing the motion activity permission on API level 30.
https://developer.android.com/training/permissions/requesting
At the same time, your app should respect the user's decision to deny a permission. Starting in Android 11 (API level 30), if the user taps Deny for a specific permission more than once during your app's lifetime of installation on a device, the user doesn't see the system permissions dialog if your app requests that permission again. The user's action implies "don't ask again." On previous versions, users would see the system permissions dialog each time your app requested a permission, unless the user had previously selected a "don't ask again" checkbox or option.
https://user-images.githubusercontent.com/2423263/139960862-1dd2cf0b-f9e5-4e20-91d5-b52f66979700.mp4
Of course, even in previous versions, the user could select "Deny and don't ask again". We need to handle all of those. Let's do that first.
https://user-images.githubusercontent.com/2423263/139961392-acd6e247-c0b9-4da9-8013-62cf82712dff.mp4
Alas, it is non-trivial to fix this. The callback in both cases does not distinguish between "denied by user" and "denied automatically by system". In other words, when we call requestPermissions
, we don't actually know whether the user will get a prompt to fix the situation or not. And if the permission is denied without showing them the prompt, they will not know what to do.
Let's explore the use of shouldShowRequestPermissionRationale
(https://developer.android.com/reference/androidx/core/app/ActivityCompat#shouldShowRequestPermissionRationale(android.app.Activity,%20java.lang.String)) which is underdocumented.
Argh! This API is just bonkers. Apparently shouldShowRequestPermissionRationale
:
Verified on android 10 (5 Deny and then "Deny and don't ask again")
2021-11-02 16:20:36.644 24234-24473/edu.berkeley.eecs.emission I/SensorPermissionsAndSettingsForegroundDelegate: shouldShowRequestPermissionRationale = false
2021-11-02 16:20:39.908 24234-24473/edu.berkeley.eecs.emission I/SensorPermissionsAndSettingsForegroundDelegate: shouldShowRequestPermissionRationale = true
2021-11-02 16:20:43.413 24234-24473/edu.berkeley.eecs.emission I/SensorPermissionsAndSettingsForegroundDelegate: shouldShowRequestPermissionRationale = true
2021-11-02 16:20:46.417 24234-24473/edu.berkeley.eecs.emission I/SensorPermissionsAndSettingsForegroundDelegate: shouldShowRequestPermissionRationale = true
2021-11-02 16:20:49.576 24234-24473/edu.berkeley.eecs.emission I/SensorPermissionsAndSettingsForegroundDelegate: shouldShowRequestPermissionRationale = true
2021-11-02 16:20:52.833 24234-24473/edu.berkeley.eecs.emission I/SensorPermissionsAndSettingsForegroundDelegate: shouldShowRequestPermissionRationale = false
This seems to be true on android 11 as well (results of using "fix" 4 times are below) - it is just that don't ask again is the default.
2021-11-02 16:17:40.803 11677-11855/edu.berkeley.eecs.emission I/SensorPermissionsAndSettingsForegroundDelegate: shouldShowRequestPermissionRationale = false
2021-11-02 16:17:43.826 11677-11855/edu.berkeley.eecs.emission I/SensorPermissionsAndSettingsForegroundDelegate: shouldShowRequestPermissionRationale = true
2021-11-02 16:17:46.458 11677-11855/edu.berkeley.eecs.emission I/SensorPermissionsAndSettingsForegroundDelegate: shouldShowRequestPermissionRationale = false
2021-11-02 16:17:49.174 11677-11855/edu.berkeley.eecs.emission I/SensorPermissionsAndSettingsForegroundDelegate: shouldShowRequestPermissionRationale = false
The workaround on SO seems to be to store whether we have asked for this permission earlier or not!!
or (accepted) to check the shouldShowRequestPermissionRationale
in the callback.
Not quite sure how the flow will work for us in the second case, though given that our UI is not completely native.
request permissions
user denies, we say "please fix again"
user fixes, we request permissions again
user denies
So basically, the native implementation can return "showPermissions" or "openAppSettings" as the status.
The UI should call fix with the appropriate setting
But how do we even get the callback? We will have to call requestPermission
first to do that.
Steps are (concretely):
shouldRequest
is true, callback with showPermissions
shouldRequest
is false, since this is in the callback, we know that the user must have denied, callback with "openAllSettings"shouldRequest
in the callback, then we can't distinguish between:Does it matter? Should we even show an error popup? If we skipped the error popup every time, this would not be a problem. But if there was an error, users like to know. Maybe we can store the value before the call and after the call and determine whether there was a popup or not!!
So we can return PopupShown
and PopupWillBeShown
from the method.
Another option might be to just always open the app permission settings. That would simplify the user interface. But it would also have a clunkier user interface for the case in which we could have the user accept the permission directly from the status screen.
Now, finally moving on to refactoring the background notification code. We can now simplify that codebase significantly. We currently have increasingly elaborate checks in the background checker that duplicate the foreground check code, but they are increasingly not resolvable automatically and require the app settings screen to be opened. Instead of complicating the logic further, now that we have done all the work of creating the app settings screen, we should just show a generic notification and then open the app settings screen when the user clicks on it.
The big question is, how do we get the appropriate javascript callback and how do we use it to automatically go to the app settings screen?
The scaffolding changes are fairly straightforward:
We already have a set of callbacks in www/js/splash/localnotify.js
which allows us to redirect on notification click. When the user clicks on the "app settings incorrect" local notification, we can redirect to the new page.
The trickiest part is ensuring that clicking on the location notification invokes the javascript callback.
The naive approach of clicking on the currently generated notification just triggered the callback in the data collection plugin, but not in javascript.
After spending multiple hours digging through the innards of the local notification plugin, I realized why. It is because the javascript callbacks are triggered only for notifications created through the local notification plugin. This callback is triggered from ClickReceiver.onClick
,
/**
* Called when local notification was clicked by the user.
*
* @param notification Wrapper around the local notification.
* @param bundle The bundled extras.
*/
@Override
public void onClick(Notification notification, Bundle bundle) {
String action = getAction();
JSONObject data = new JSONObject();
setTextInput(action, data);
launchAppIf();
fireEvent(action, notification, data);
if (notification.getOptions().isSticky())
return;
if (isLast()) {
notification.cancel();
} else {
notification.clear();
}
}
The confusing part is that, although ClickReceiver
is called a Receiver
, it is actually an activity
public class ClickReceiver extends AbstractClickReceiver {
abstract public class AbstractClickReceiver extends Activity {
<activity
android:name="de.appplant.cordova.plugin.localnotification.ClickReceiver"
android:launchMode="singleInstance"
android:theme="@android:style/Theme.Translucent"
android:exported="false" />
And is in turn invoked because the pending intent for the notification starts the ClickActivity
instead of the main activity for the app.
Intent intent = new Intent(context, clickActivity)
.putExtra(Notification.EXTRA_ID, options.getId())
.putExtra(Action.EXTRA_ID, action.getId())
.putExtra(Options.EXTRA_LAUNCH, action.isLaunchingApp())
.setFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
Our existing background notifications launch the main activity of the app, so they don't trigger the local notification callbacks.
public static void generateLocationEnableNotification(Context ctxt) {
Intent activityIntent = new Intent(ctxt, MainActivity.class);
activityIntent.setAction(SensorControlConstants.ENABLE_LOCATION_PERMISSION_ACTION);
PendingIntent pi = PendingIntent.getActivity(ctxt, SensorControlConstants.ENABLE_LOCATION_PERMISSION,
activityIntent, PendingIntent.FLAG_UPDATE_CURRENT);
NotificationHelper.createNotification(ctxt, SensorControlConstants.ENABLE_LOCATION_PERMISSION,
ctxt.getString(R.string.location_permission_off_enable),
pi);
}
There are two potential high level solutions:
While we may want to go with (2) over the long-term if we stop supporting Apache Cordova, it is clearly a silly solution for the short-term.
Given that we are going to go with (1), we still have two options:
I think we should go with (2) in this case, because the Foreground Delegate is strongly enmeshed with the cordova framework and callbacks, but the background checker is not. This separation makes it easier to adapt to other UI mechanisms in the future.
we should also consider how to generate the local notification.
schedule
method in the plugin, but unfortunately, that is private. LocalNotify.execute
with the schedule parameters but it requires a cordovaCallback to work and since we click on the notification outside the cordova context, cordovaCallback = null
in the when the click in processed in onNewIntent
.The next step is to call it directly using the notification manager in the local notification code. This is what we currently do in the transition notify code. https://github.com/e-mission/e-mission-transition-notify/blob/4f32000935fe73f94d60ef68281eb2f3a343083a/src/android/TransitionNotificationReceiver.java#L172
TODO: Pull out the common dispatch functionality to NotificationHelper or similar so we can generate local notifications from anywhere in our codebase.
Pulled out the common dispatch functionality, but haven't yet converted the TripEnd notification code to use the new functionality, since I don't have an easy way to test it right now. We also don't know whether we want to retain the trip end notification code since it has not been very widely used so far.
Filed issue to clean it up https://github.com/e-mission/e-mission-transition-notify/issues/32
Changes for background sensing done. In addition to the data collection plugin, also needed changes to: https://github.com/e-mission/cordova-unified-logger/pull/35 and https://github.com/e-mission/cordova-usercache/pull/43
Leaving them open for now while we see if there are any other changes required before the merge.
Integrated the app status (at least in basic fashion) into the control screen (https://github.com/e-mission/e-mission-phone/pull/804/commits/539a0fdc9df8af2b436dd23a852364bf50d0c1b1) Now if I can just have the modal popup automatically when the user clicks on the notification, this is demo-worthy.
Tried creating a new state with the same controller and view
.state('root.main.control.appstatus', {
url: '/control/appstatus',
views: {
'main-control': {
templateUrl: 'templates/control/main-control.html',
controller: 'ControlCtrl'
}
}
})
and then redirecting to it. But this naive approach doesn't work since
ionic.bundle.js:26794 Error: Could not resolve 'root.main.control.appstatus ' from state 'root.main.control'
at Object.transitionTo (ionic.bundle.js:52013)
at Object.go (ionic.bundle.js:51946)
at Object.localNotify.handleLaunch (localnotify.js:28)
at localnotify.js:52
at processQueue (ionic.bundle.js:29127)
at ionic.bundle.js:29143
at Scope.$eval (ionic.bundle.js:30395)
at Scope.$digest (ionic.bundle.js:30211)
at ChildScope.$apply (ionic.bundle.js:30503)
at HTMLButtonElement.<anonymous> (ionic.bundle.js:65426)
switching back to a single state, but with the params defined outside the URL https://github.com/angular-ui/ui-router/wiki/URL-Routing#using-parameters-without-specifying-them-in-state-urls
.state('root.main.control', {
url: '/control',
params: {
launchAppStatusModal: false
},
views: {
'main-control': {
templateUrl: 'templates/control/main-control.html',
controller: 'ControlCtrl'
}
}
Bingo!! That worked!!
afterEnter called with stateparams launchAppStatusModal: false
afterEnter called with stateparams launchAppStatusModal: true
Will check in and publish tomorrow and then try to debug the case of the app status icon in the profile page.
At this point, the code is good enough for demo purposes but there are still some hacks that need to be investigated/fixed:
waiting_for_trip_start
and tracking_error
when the motion activity is turned offcancelAll
But it is probably good enough to push out as a beta version and get feedback.
One more issue: The hook to rewrite the package name for R
does not seem to work for the new file /Users/kshankar/e-mission/e-mission-phone/plugins/cordova-plugin-em-datacollection/src/android/verification/SensorControlChecks.java
walk callback with files = ..../Users/kshankar/e-mission/e-mission-phone/plugins/cordova-plugin-em-datacollection/src/android/verification/SensorControlChecks.java...
file SensorControlBackgroundChecker.java needs to be rewritten, checking package
Handling package: edu.berkeley.eecs.emission.cordova.tracker.verification
sourceFile: /Users/kshankar/e-mission/e-mission-phone/platforms/android/app/src/main/java/edu/berkeley/eecs/emission/cordova/tracker/verification/SensorControlBackgroundChecker.java
file SensorControlForegroundDelegate.java needs to be rewritten, checking package
Handling package: edu.berkeley.eecs.emission.cordova.tracker.verification
sourceFile: /Users/kshankar/e-mission/e-mission-phone/platforms/android/app/src/main/java/edu/berkeley/eecs/emission/cordova/tracker/verification/SensorControlForegroundDelegate.java
Manually fixing and moving on for now..
We should also add a reconfigured build to the CI to catch this in the future.
Another huge issue is that we appear to get a tracking_error generated at app launch. This generates a weird error message and a message saying that we need to click on it to resolve!! And the message is never removed, even after the user fixed everything and consents.
Tried multiple times to figure out where we are getting the tracking error from, but no luck in the time available. Hacking around this for now by making changes only to markConsented
but need to figure out the whole init workflow and streamline it.
Added hack
With the hack:
This is bizarre and freaky but I'm not going to look a gift horse in the mouth now. Tested on android 8 as well; starting the upgrade process now...
Feedback from Sandee:
After many months, and fixing a couple of side issues (https://github.com/e-mission/e-mission-phone/pull/804/commits/4378a70d9638f0e2bf6647c180d95e3061dcbbe4, https://github.com/e-mission/e-mission-phone/pull/805, https://github.com/e-mission/e-mission-phone/pull/808, https://github.com/e-mission/e-mission-phone/pull/809) let's return here and:
Starting with https://github.com/e-mission/e-mission-docs/issues/680#issuecomment-971825731, the hook searches for
const R_RE = /[^\.\w]R\./;
...
if (contents.match(R_RE) || contents.match(BUILDCONFIG_RE) || contents.match(MAINACT_RE)) {
console.log('file '+filename+' needs to be rewritten, checking package');
but the R package is actually unused in SensorControlChecks.java
. So there is no R.
and the match fails. The solution is to remove the unused import.
wrt
Will check in and publish tomorrow and then try to debug the case of the app status icon in the profile page.
not sure what the problem with the app status icon on the profile page is.
Ah I think it is related to:
overallAppStatus on the profile page is not getting updated for this to work
when it should be updated from
<permissioncheck overallstatus="overallAppStatus"></permissioncheck>
I don't think that it a super high priority right now since users can also use the button to check app status, not only fix it.
I looked into it a little more, and we do in fact compute the overallAppStatus in the app status directive controller. However, because it is not correctly linked to the
overallAppStatus` field in the profile scope, nothing gets updated correctly. I'm going to try one more time to fix it, but otherwise, I am moving on.
Note that the overallAppStatus
in the directive appears to be correct, so if I change the onClick to ng-click="appStatusChecked(overallAppStatus)"
and add a new line in the callback, the status is updated correctly. Need to figure out whether we can return any value during modal initialization.
+ $scope.overallAppStatus = newStatus;
I tried to read the value from the modal once it was created. Unfortunately, the modal is returned before the status is recomputed.
I then added a breakpoint into another function and invoked if after the modal was initialized. The overallAppStatus
still was false
.
This is going to take too much time, and it is complicated and potentially angular1
specific. I think that the correct solution is to refactor the app status module to be a service and then read the value from the service, but the ROI on that is not great.
Here's the change to restore it in case we need it again.
seemingly infinite loop between waiting_for_trip_start and tracking_error when the motion activity is turned off
we should definitely fix this and this
Another huge issue is that we appear to get a tracking_error generated at app launch. This generates a weird error message and a message saying that we need to click on it to resolve!! And the message is never removed, even after the user fixed everything and consents.
and then I think we should move on to iOS
We start the periodic sync
2021-07-27 16:12:45.427 22374-22921/edu.berkeley.eecs.emission I/TripDiaryStateMachineRcvr: START PERIODIC ACTIVITY
Foreground service starts
2021-07-27 16:12:49.795 22374-22921/edu.berkeley.eecs.emission D/TripDiaryStateMachineForegroundService: startProperly called with context = edu.berkeley.eecs.emission.MainActivity@b3efb44
2021-07-27 16:12:49.828 22374-22374/edu.berkeley.eecs.emission D/TripDiaryStateMachineForegroundService: onCreate called
2021-07-27 16:12:49.842 22374-22374/edu.berkeley.eecs.emission D/TripDiaryStateMachineForegroundService: onStartCommand called with intent = Intent { cmp=edu.berkeley.eecs.emission/.cordova.tracker.location.TripDiaryStateMachineForegroundService } flags = 0 and startId = 1
We generate an "initialize"
TripDiaryStateMachineForegroundService.startProperly(ctxt);
// It is not enough to move to the foreground; you also need to reinitialize tracking
// https://github.com/e-mission/e-mission-docs/issues/580#issuecomment-700747931
ctxt.sendBroadcast(new ExplicitIntent(ctxt, R.string.transition_initialize));
And we launch a bunch of checks for the app state
if (intent == null) {
SensorControlBackgroundChecker.checkAppState(this);
message = humanizeState(this, TripDiaryStateMachineService.getState(this));
}
So far, so good.
The checks indicate that motion activity is turned off and so we generated another notification to fix it.
2021-07-27 16:12:51.637 22374-22921/edu.berkeley.eecs.emission I/SensorControlBackgroundChecker: Curr status check results = loc permission, motion permission, notification, unused apps [true, false, true, true]
2021-07-27 16:12:51.780 22374-22921/edu.berkeley.eecs.emission D/NotificationHelper: Generating notify with id 362253739, message Click to view and fix app status and pending intent PendingIntent{1278d18: android.os.BinderProxy@3649371}
We receive and handle the initialize, note that it uses a new thread (22374-22374)
2021-07-27 16:13:04.370 22374-22374/edu.berkeley.eecs.emission I/TripDiaryStateMachineRcvr: TripDiaryStateMachineReciever onReceive(android.app.ReceiverRestrictedContext@eaf447e, Intent { act=local.transition.initialize flg=0x10 pkg=edu.berkeley.eecs.emission cmp=edu.berkeley.eecs.emission/.cordova.tracker.location.TripDiaryStateMachineReceiver }) called
2021-07-27 16:13:16.758 22374-22921/edu.berkeley.eecs.emission I/TripDiaryStateMachineRcvr: END PERIODIC ACTIVITY
We generate another start command because we received the initialize
if (intent.getAction().equals(context.getString(R.string.transition_initialize))) {
...
TripDiaryStateMachineForegroundService.startProperly(context);
}
2021-07-27 16:13:19.818 22374-22374/edu.berkeley.eecs.emission D/TripDiaryStateMachineForegroundService: startProperly called with context = android.app.ReceiverRestrictedContext@eaf447e
2021-07-27 16:13:25.027 22374-22374/edu.berkeley.eecs.emission D/TripDiaryStateMachineForegroundService: onStartCommand called with intent = Intent { cmp=edu.berkeley.eecs.emission/.cordova.tracker.location.TripDiaryStateMachineForegroundService } flags = 0 and startId = 2
Step 1: We should consider removing this circular dependency.
checkForegroundNotification
):
TripDiaryStateMachineForegroundService.startProperly(ctxt);
// It is not enough to move to the foreground; you also need to reinitialize tracking
// https://github.com/e-mission/e-mission-docs/issues/580#issuecomment-700747931
ctxt.sendBroadcast(new ExplicitIntent(ctxt, R.string.transition_initialize));
if (intent.getAction().equals(context.getString(R.string.transition_initialize))) {
...
TripDiaryStateMachineForegroundService.startProperly(context);
}
Fortunately, in startProperly
, we don't re-send the initialize, but this
callback seems unnecessary. From my last minute frantic debugging, this
might also be the reason for
https://github.com/e-mission/e-mission-docs/issues/680#issuecomment-971953421
Let's revert the hack, see if this is the case and then see how to fix it.
reverted hack and fixed, picking back up on infinite loop
startProperly
if the initialize was generated from the foreground service. But let's fix the actual problem first, and then work on the niceties.Picking up after the startProperly call, we restart the service
2021-07-27 16:13:25.027 22374-22374/edu.berkeley.eecs.emission D/TripDiaryStateMachineForegroundService: onStartCommand called with intent = Intent { cmp=edu.berkeley.eecs.emission/.cordova.tracker.location.TripDiaryStateMachineForegroundService } flags = 0 and startId = 2
Next, we handle the initialize and begin re-creating the geofence
2021-07-27 16:13:25.272 22374-22374/edu.berkeley.eecs.emission D/TripDiaryStateMachineService: handleAction(local.state.waiting_for_trip_start, local.transition.initialize) called
...
2021-07-27 16:13:25.661 22374-22983/edu.berkeley.eecs.emission D/CreateGeofenceAction: About to start waiting for location
In parallel, we start handling the tracking_error
that we generated earlier from the checkAppStatus after re-creating the foreground service.
2021-07-27 16:13:56.783 22374-22374/edu.berkeley.eecs.emission I/TripDiaryStateMachineRcvr: TripDiaryStateMachineReciever onReceive(android.app.ReceiverRestrictedContext@eaf447e, Intent { act=local.transition.tracking_error flg=0x10 pkg=edu.berkeley.eecs.emission cmp=edu.berkeley.eecs.emission/.cordova.tracker.location.TripDiaryStateMachineReceiver }) called
we get back the location and create the geofence
2021-07-27 16:15:37.512 22374-22374/edu.berkeley.eecs.emission D/TripDiaryStateMachineService: newState saved in prefManager is local.state.waiting_for_trip_start
as part of the regular checks after setting the state, we notice that the motion activity is bad, so we set the new state to start
2021-07-27 16:16:39.656 22374-22374/edu.berkeley.eecs.emission I/SensorControlBackgroundChecker: Curr status check results = loc permission, motion permission, notification, unused apps [true, false, true, true]
2021-07-27 16:16:41.331 22374-22374/edu.berkeley.eecs.emission D/NotificationHelper: Generating notify with id 362253739, message Click to view and fix app status and pending intent PendingIntent{280b2ec: android.os.BinderProxy@5d846b5}
2021-07-27 16:17:30.871 22374-22374/edu.berkeley.eecs.emission D/TripDiaryStateMachineService: newState after handling action is local.state.start
Then we check the settings again
2021-07-27 16:17:34.264 22374-22374/edu.berkeley.eecs.emission I/SensorControlBackgroundChecker: Curr status check results = loc permission, motion permission, notification, unused apps [true, false, true, true]
2021-07-27 16:17:34.448 22374-22374/edu.berkeley.eecs.emission D/NotificationHelper: Generating notify with id 362253739, message Click to view and fix app status and pending intent PendingIntent{54c7384: android.os.BinderProxy@5d846b5}
But now we are in the start state, so we restart the FSM by generating an initialize
2021-07-27 16:17:34.471 22374-22374/edu.berkeley.eecs.emission I/SensorControlBackgroundChecker: in restartFSMIfStartState, currState = local.state.start
2021-07-27 16:17:34.483 22374-22374/edu.berkeley.eecs.emission I/SensorControlBackgroundChecker: in start state, sending initialize
So the loop is:
waiting_for_trip_start
setNewState
, check settings, find that motion activity is a problem,
generate tracking_error
, move to start
statesetNewState
, check settings, find that motion activity is a problem,
try to restart the FSM by generating an initialize because we are in start statewaiting_for_trip_start
The real problem is that we are in two minds about how to treat non location permissions. In the FSM, we treat them as optional, so once the geofence is created, we go to waiting_for_trip_start
. In the sensor control checks, we treat them as required, so we generate a tracking error if they are not present.
We need to figure out how we want to represent these. This will involve community input, so I have created https://github.com/e-mission/e-mission-docs/issues/700 to track it. For now, we will treat them as optional, which means that we only need to generate a tracking error if the location is turned off.
Last remaining issues on android:
At long last, after clearing the decks with https://github.com/e-mission/e-mission-phone/pull/799 https://github.com/e-mission/e-mission-phone/pull/800 https://github.com/e-mission/e-mission-phone/pull/801 https://github.com/e-mission/e-mission-phone/pull/802
I'm ready to start the annual ritual of upgrading to the most recent version of everything. I will also plan to address https://github.com/e-mission/e-mission-docs/issues/678 and add silent push notifications to android as well to address https://github.com/e-mission/e-mission-docs/issues/677