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

Referral Notifications - Background Sync #2680

Merged
merged 55 commits into from
Oct 3, 2023
Merged

Conversation

avazirna
Copy link
Contributor

@avazirna avazirna commented Jul 17, 2023

Summary

This is the second PR related with the implementation of the Referral Notification feature. This part is responsible for the background sync. Background syncs are triggered after a successful delivery of a FCM message and when there is an active user session. The main goal of the sync is to allow the user access to newly created or updated referral cases without any action from the user.

Product Description

The way the background sync was planned is to be the least disruptive to the users, so there are aspects before, during and after a sync.

  • Before a sync is triggered
    • The first check is whether there is an active session, if not, a blocking sync is triggered in the next login;
    • The second task is to ensure that the user is in a sync-safe feature, at this point the main issue is Form Entries. So when a new request comes in and the user is in the middle of a form entry, a dialog is shown to the user informing of a pending sync (see image below). This sync will be triggered after the form submission and once the user is in a safe place.

Sync attempt during Form Entry

  • During a sync:
    • A notification with a progress bar will be presented to the user showing about the state of the sync;
    • The user is blocked from initiating any sync-unsafe operations, such as triggering another sync, logging out and opening a form (others can be added to the list).

Block unsafe operation during sync

  • After a successful sync - CommCare checks the current state of the app and refreshes the UI accordingly (see videos below). Main places here are around EntitySelectActivity, EntityDetailActivity and MenuActivity (this list also increase).
post_sync_entityselect_case_update.mov
post_sync_entitydetail_case_update.mov
post_sync_entitydetail_case_closed.mov

Safety Assurance

  • If the PR is high risk, "High Risk" label is set
  • I have confidence that this PR will not introduce a regression for the reasons below
  • Do we need to enhance manual QA test coverage ? If yes, "QA Note" label is set correctly

Notes for reviewers

  • Overall, the safety of the feature lies on preventing any sync from being triggered when the user is on a sync-unsafe operation, this is an area I would like particular attention to make sure that all such places are covered.
  • Currently, the user is not blocked during the post background sync phase, I can see a few potential risks there as the user is free to navigate to any place in the app. An approach could be to have a spinner and block the UI.
  • The background sync is currently skipping fixture and schema blocks of the restore file. I wonder if it makes sense to have a flag preventing these from being in the restore file.

Cross-request: dimagi/commcare-core#1310

@avazirna avazirna added QA Note High Risk If the PR introduce high risk changes that has high probability of introducing breaking changes labels Jul 17, 2023
@avazirna avazirna requested a review from shubham1g5 July 17, 2023 10:23
@avazirna avazirna changed the title [WIP] Referral Notifications - Background Sync Referral Notifications - Background Sync Jul 17, 2023
@shubham1g5
Copy link
Contributor

@avazirna Thanks for Pring this. Some initial feedback without going in code -

  • Can we rebase this PR over Push notifications PR and make the base branch on this PR fcm_push_notifications so that it's easier to review this PR and separate out the reviews for 2 PRs

Currently, the user is not blocked during the post background sync phase, I can see a few potential risks there as the user is free to navigate to any place in the app. An approach could be to have a spinner and block the UI.

what does post background sync phase mean here ? Is this when the sync actually gets applied to change the case data or something else.

The background sync is currently skipping fixture and schema blocks of the restore file. I wonder if it makes sense to have a flag preventing these from being in the restore file.

Does the next manual sync restores the skipped data to the correct state ? Asking because HQ thinks it already send it to mobile so it might skip it on next manual sync. Othwerise I agree that we should have a API flag to skip this data as it doesn't make sense to download data if we are not going to use it.

@avazirna
Copy link
Contributor Author

Thanks for the notes @shubham1g5

  • Can we rebase this PR over Push notifications PR and make the base branch on this PR fcm_push_notifications so that it's easier to review this PR and separate out the reviews for 2 PRs

Parking here a few notes from our chat, I'm going to focus on merging the Push notifications PR and then change the base of this PR.

Currently, the user is not blocked during the post background sync phase, I can see a few potential risks there as the user is free to navigate to any place in the app. An approach could be to have a spinner and block the UI.

what does post background sync phase mean here ? Is this when the sync actually gets applied to change the case data or something else.

The post background sync phase is basically around triggering the necessary UI updates depending on the activity that is on the foreground, i.e. at this point the sync is finished and the user sandbox has been updated

The background sync is currently skipping fixture and schema blocks of the restore file. I wonder if it makes sense to have a flag preventing these from being in the restore file.

Does the next manual sync restores the skipped data to the correct state ? Asking because HQ thinks it already send it to mobile so it might skip it on next manual sync. Othwerise I agree that we should have a API flag to skip this data as it doesn't make sense to download data if we are not going to use it.

Yes, only the background sync skips these elements.

@avazirna
Copy link
Contributor Author

@damagatchi retest this please

@shubham1g5
Copy link
Contributor

I wonder if it makes sense to have a flag preventing these from being in the restore file.

Looks like there is already a flag that FP uses called skip_fixtures for restores, so maybe we can utilise that here as well.

@avazirna avazirna changed the base branch from master to fcm_push_notifications July 27, 2023 18:19
@@ -130,6 +130,7 @@ public abstract class CommCareActivity<R> extends AppCompatActivity
private boolean isMainScreenBlocked;

DataSyncCompleteBroadcastReceiver dataSyncCompleteBroadcastReceiver = new DataSyncCompleteBroadcastReceiver();
private boolean dataSyncCompleteBroadcastReceiverRegistered = false;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. I don't think this flag is needed.
  2. Only activities that use this broadcast should register this broadcast instead of registering it for every CommCareActivity

we can instead do -

 if (isBackgroundSyncEnabled() and shouldListenToSyncComplete()) {
            dataSyncCompleteBroadcastReceiver = new DataSyncCompleteBroadcastReceiver()
            registerReceiver(dataSyncCompleteBroadcastReceiver, new IntentFilter(COMMCARE_DATA_UPDATE_ACTION));
}

// Activities that needs to refresh after sync should override this to return true
public boolean shouldListenToSyncComplete() {
return false;
}

and

if(dataSyncCompleteBroadcastReceiver!=null){
 unregisterReceiver(dataSyncCompleteBroadcastReceiver);
}

@@ -565,30 +583,28 @@ public static void markRawMediaCleanUpComplete() {
.edit().putBoolean(RAW_MEDIA_CLEANUP_COMPLETE, true).apply();
}

public static boolean isPendingSyncRequestFromServer() {
public static boolean isPendingSyncRequestFromServerForUser() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. this method should ideally take username as a method parameter so that we don't cause session exceptions in here.
  2. Confirming that loggedusername is not the full username like - test@domain.commcarehq.org ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. This is to check if there is a pending sync for the current logged in user, it's triggered right after a successful login. I can rename it to make this more clear.
  2. Confirmed.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand, although my suggestion is to avoid hidden session exception if in future we call this method from other places. Supplying username to this method will ensure that caller of this method will handle implications of session not being available at that time.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a good point

return;
}
// Attempt to trigger the sync if the user is currently in a sync safe activity
if (isCurrentActivitySyncSafe()){
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this check actually needs to happen for restore and not form submission.

}

private boolean isCurrentActivitySyncSafe() {
return !syncUnsafeActivities.contains(CommCareApplication.currentActivityName);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of setting the currentActivityName in CommCareApplication, you can directly have a variable to set backgroundSyncSafe that you can update from FormEntryActivity . This way every activity is deciding whether it can handle background sync instead of having to maintain a centralised list of classes in this class.


private static final String ENABLE_ANDROID_WINDOW_SECURE_FLAG = "cc-enable-android-window-secure-flag";

private static final String PENDING_SYNC_REQUEST_FROM_SERVER_TIME = "pending-sync-request-from-server-time";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unused.

@@ -96,11 +100,22 @@ public class HiddenPreferences {
private static final long NO_OF_HOURS_TO_WAIT_TO_RESUME_BACKGROUND_WORK = 36;

// This is to be used by CommCareFirebaseMessagingService to schedule a sync after the next Login
public final static String PENDING_SYNC_REQUEST_FROM_SERVER = "pending-sync-request-from-server";
public final static String PENDING_SYNC_REQUEST_FROM_SERVER = "pending-sync-request-from-server-";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: can just call it this BACKGROUND_SYNC_PENDING and accordingly rename the related methods that access the preference.

@Override
public void alertPendingSync(FCMMessageData fcmMessageData) {
if (!HiddenPreferences.isPostFormSubmissionSyncNeeded()) {
HiddenPreferences.setPostFormSubmissionSyncNeeded(true);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wondering whether we need a separate preference to track this like why can't we use the PENDING_SYNC_REQUEST_FROM_SERVER here as well ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I thought about this too, the thing is that the now BACKGROUND_SYNC_PENDING shared preference is concatenated with the username, to ensure that we trigger the sync for the right user. Consolidating both would mean to make sure that this continues to work on devices shared by different users.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consolidating both would mean to make sure that this continues to work on devices shared by different users.

That's a good thing right ?

@@ -168,6 +171,19 @@ public abstract class HomeScreenBaseActivity<T> extends SyncCapableCommCareActiv
protected boolean showCommCareUpdateMenu = false;
private static final int MAX_CC_UPDATE_CANCELLATION = 3;

// This is to trigger a background sync after a form submission,
public static boolean shouldTriggerBackgroundSync = true;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should be private boolean shouldTriggerBackgroundSync

@@ -1249,9 +1299,15 @@ public void onResumeSessionSafe() {
attemptDispatchHomeScreen();
}

// In case a Sync was blocked because of a form entry, trigger now if it's safe
if (HiddenPreferences.isPostFormSubmissionSyncNeeded() && shouldTriggerBackgroundSync) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Won't shouldTriggerBackgroundSync will always be false here when coming back from From Entry ? If so, how does the sync gets initiated after form submission ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@shubham1g5 Not necessarily, when we open a form onActivityResultSessionSafe from HomeScreenBaseActivity in called, this then triggers the logic to start a new Form Entry session, and here we set shouldTriggerBackgroundSync to false, so by the time onResumeSessionSafe is called, shouldTriggerBackgroundSync is false and the sync is not triggered. Also, during onResumeSessionSafe, shouldTriggerBackgroundSync is reset to true, which means that when onResumeSessionSafe is called again, the sync can be triggered (if necessary), unless a new Form Entry Session is starts.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

which means that when onResumeSessionSafe is called again, the sync can be triggered (if necessary), unless a new Form Entry Session is starts.

But that means that sync won't get triggered immediately after user completes the form but only when user comes back to home screen one more time.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@shubham1g5 I think we are supposed to go back to HomeScreenBaseActivity after a form submission to determine the next step. Besides, we start a FormEntry activity for result in HomeScreenBaseActivity. Are there other scenarios that I'm missing here?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am considering the scenario where a user go through this navigation -

  1. Home Screen -> Start -> Module -> Form Entry
  2. Sync request from server
  3. Form Entry Complete
  4. Home Screen (no eof navigation) but no sync
  5. User goes out and back into the home screen -> Sync gets triggered.

Is my interpretaion of sync behaviour in step 4 and 5 right based on the current code ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why wouldn't there be a sync in number 4? After a form submission, we would navigate through HomeSreenBaseActivity and shouldTriggerBackgroundSync would be true, so a sync would be triggered

Copy link
Contributor

@shubham1g5 shubham1g5 Aug 31, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see, My understanding is that HomeSreenBaseActivity doesn't reinitialise when onActivityResult gets called on the activity as there might already be a live instance of that activity in the background. But I might be wrong here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I think so too, but why does it need to be reinitialized for the sync to occur?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shouldTriggerBackgroundSync is set to false in startFormEntry and will remain false without HomeSreenBaseActivity getting reinitialised after step 3 above.

import org.commcare.activities.HomeScreenBaseActivity;
import org.commcare.models.AndroidSessionWrapper;
import org.commcare.session.CommCareSession;
import org.commcare.session.SessionDescriptorUtil;
import org.commcare.session.SessionFrame;

public class DataSyncCompleteBroadcastReceiver extends BroadcastReceiver {

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

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this whole class can be simplified to -

    @Override
    public void onReceive(Context context, Intent intent) {
        AndroidSessionWrapper asw = CommCareApplication.instance().getCurrentSessionWrapper();
        Activity activity = (Activity)context;
        asw.cleanVolatiles();
        Intent returnIntent = new Intent();
        setSelectedEntity(activity, returnIntent);
        activity.setResult(RESULT_RESTART, returnIntent);
        activity.finish();
    }
    
    private void setSelectedEntity(Activity activity, Intent returnIntent) {
        if (activity instanceof EntityDetailActivity &&
                activity.getIntent().hasExtra(SessionFrame.STATE_DATUM_VAL)) {
            String selectedEntity = activity.getIntent().getStringExtra(SessionFrame.STATE_DATUM_VAL);
            returnIntent.putExtra(EXTRA_ENTITY_KEY, selectedEntity);
        }
    }

And then you can set the selectedEntityPostSync in onActivityResult of HomeScreenBaseActivity and make it a private String selectedEntityPostSync variable of activity. public static variables in an activity should be avoided and they are mostly should only be used for constants.

@@ -196,7 +196,7 @@ public class CommCareApplication extends MultiDexApplication {
private boolean invalidateCacheOnRestore;
private CommCareNoficationManager noficationManager;

public static String currentActivityName;
public static boolean backgroundSyncSafe;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this doesn't need to be static, you can set it as CommCareApplication.instance().setIsBackgroundSyncSafe(false)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

true, on it

@@ -586,18 +584,18 @@ public static void markRawMediaCleanUpComplete() {
public static boolean isPendingSyncRequestFromServerForUser() {
String loggedUsername = CommCareApplication.instance().getSession().getLoggedInUser().getUsername();
return PreferenceManager.getDefaultSharedPreferences(CommCareApplication.instance())
.contains(PENDING_SYNC_REQUEST_FROM_SERVER + loggedUsername + "@"+ getUserDomainWithoutServerUrl());
.contains(BACKGROUND_SYNC_PENDING + loggedUsername + "@"+ getUserDomainWithoutServerUrl());
}
public static void setPendingSyncRequestFromServerForUser(FCMMessageData fcmMessageData) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: can rename all methods for BACKGROUND_SYNC_PENDING as well - setBackgroundSyncPending(), isBackgroundSyncPending() and clearBackgroundSyncPending()

@Override
public void alertPendingSync(FCMMessageData fcmMessageData) {
if (!HiddenPreferences.isPostFormSubmissionSyncNeeded()) {
HiddenPreferences.setPostFormSubmissionSyncNeeded(true);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consolidating both would mean to make sure that this continues to work on devices shared by different users.

That's a good thing right ?

try {
return new DateTime(timeInISO8601);
} catch (Exception e) {
return new DateTime();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should log the exception to sumo here so that we get to know about these issues

@@ -1249,9 +1299,15 @@ public void onResumeSessionSafe() {
attemptDispatchHomeScreen();
}

// In case a Sync was blocked because of a form entry, trigger now if it's safe
if (HiddenPreferences.isPostFormSubmissionSyncNeeded() && shouldTriggerBackgroundSync) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shouldTriggerBackgroundSync is set to false in startFormEntry and will remain false without HomeSreenBaseActivity getting reinitialised after step 3 above.

Base automatically changed from fcm_push_notifications to master September 7, 2023 15:32
shubham1g5
shubham1g5 previously approved these changes Sep 7, 2023
Copy link
Contributor

@shubham1g5 shubham1g5 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new changes looks good, we need to resolve conflicts and ensure build passes on it before merge.

@avazirna avazirna merged commit fa55951 into master Oct 3, 2023
4 of 9 checks passed
@avazirna avazirna deleted the fcm_background_sync branch October 3, 2023 23:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
High Risk If the PR introduce high risk changes that has high probability of introducing breaking changes QA Note Release Note
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants