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
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
f865524
Add FCM Data Syncer
avazirna Jul 7, 2023
8fc36d8
Assess current state of the app
avazirna Jul 7, 2023
2500bb5
Alert user about pending sync
avazirna Jul 7, 2023
13c1ca4
Block unsafe events when a background sync is underway
avazirna Jul 9, 2023
491e08d
Trigger post login pending sync
avazirna Jul 10, 2023
b363687
Publish background sync updates
avazirna Jul 10, 2023
8652d7b
Disable pending sync
avazirna Jul 10, 2023
076adaa
Cancel unnecessary sync
avazirna Jul 10, 2023
1080733
Finalize background sync
avazirna Jul 12, 2023
e5317c7
Skip certain restore file elements from being parsed
avazirna Jul 13, 2023
7f137fa
Refactor
avazirna Jul 14, 2023
13cf532
Make FCM Message data model externalizable
avazirna Jul 14, 2023
d72ed7c
Store FCM Message data for post form submission sync
avazirna Jul 14, 2023
1b529f3
Trigger sync post form submission
avazirna Jul 14, 2023
106b465
Trigger background sync
avazirna Jul 14, 2023
11516b7
Skip fixtures
avazirna Jul 26, 2023
1194304
Remove skip parsing logic
avazirna Jul 26, 2023
e6a6ea6
Lint
avazirna Aug 4, 2023
c61d023
Refactor
avazirna Aug 4, 2023
722b24f
Trigger broadcast about pending sync
avazirna Aug 4, 2023
78439ad
Refactor
avazirna Aug 4, 2023
d2e4af1
Broadcast sync complete to CommCare
avazirna Aug 7, 2023
d683391
Enable Data update receiver when background sync is enabled
avazirna Aug 7, 2023
2f0f48b
Broadcast data update for background syncs
avazirna Aug 7, 2023
0bc0b04
Refactor
avazirna Aug 11, 2023
a13afe8
Revert
avazirna Aug 14, 2023
0c2f463
Rebuild session after sync
avazirna Aug 14, 2023
308a828
Make post login sync non-user triggered
avazirna Aug 14, 2023
7ca96a8
Remove unnecessary lock
avazirna Aug 14, 2023
51510e4
Upload unsent forms
avazirna Aug 16, 2023
6e0d9d7
Update tests
avazirna Aug 21, 2023
7ed33cf
Lint
avazirna Aug 21, 2023
71a7306
Update FCM string resource keys
avazirna Aug 22, 2023
dfbe5ec
Add flag to monitor sync complete broadcast registration
avazirna Aug 22, 2023
1c84b3a
Revert
avazirna Aug 22, 2023
c0412b5
Restart activity after background sync
avazirna Aug 23, 2023
e699380
Lint
avazirna Aug 23, 2023
cfcd5dc
Refactor
avazirna Aug 23, 2023
6d42c01
Lint
avazirna Aug 23, 2023
414cccb
Set ForegroundInfo for expedited work
avazirna Aug 24, 2023
c7a70c1
Remove observer after work completion
avazirna Aug 24, 2023
cd27032
Revert
avazirna Aug 30, 2023
b9525ed
Nit
avazirna Aug 30, 2023
68e05d0
Refactor
avazirna Aug 30, 2023
415bc8b
Set activities that should restart after sync
avazirna Aug 31, 2023
f6416f3
Refactor
avazirna Aug 31, 2023
bb99c16
Refactor
avazirna Aug 31, 2023
30e2de6
Handle malformed dates
avazirna Aug 31, 2023
e1daf72
Refactor
avazirna Aug 31, 2023
ff10165
Refactor
avazirna Sep 4, 2023
be12087
Merge shared preferences post-form-submission-sync and background-syn…
avazirna Sep 4, 2023
edfce31
Log exception
avazirna Sep 4, 2023
65cc28e
Resolve merge conflicts
avazirna Oct 3, 2023
f1fbb2a
Fix unit test
avazirna Oct 3, 2023
d96060d
Android 13 notification change
avazirna Oct 3, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions app/assets/locales/android_translatable_strings.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ option.yes=YES
option.no=NO
option.cancel=CANCEL
dialog.ok=OK
dialog.do.not.show=Don't show again

updates.found=Updates found! Downloading new resource ${0} done of ${1}
updates.success=App is up to date
Expand Down Expand Up @@ -970,6 +971,13 @@ tts.speak.failed=Couldn't speak now. Please try again!
file.oversize.error.title=File Too Large
file.oversize.error.message=Selected file exceeds the file size limit of 15 MB. Please select a file whose size is less than 15 MB.

background.sync.pending.form.entry.title=Pending Sync
background.sync.pending.form.entry.detail=There are new cases available but the Sync can't run while a form is open. A Sync will be triggered upon form submission!
background.sync.logout.attempt.during.sync=Can't log out while a Sync is underway!
background.sync.user.sync.attempt.during.sync=Can't trigger a Sync while another is underway!
background.sync.form.entry.attempt.during.sync=A Form can't be open while a Sync is underway!
background.sync.fail=Background sync failed. Please try to trigger a normal Sync

android.package.name.org.commcare.dalvik.reminders=CommCare Reminders
android.package.name.callout.commcare.org.sendussd=Commcare USSD
android.package.name.org.commcare.dalvik.abha=CommCare ABHA
11 changes: 11 additions & 0 deletions app/res/layout/custom_alert_dialog.xml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,17 @@
android:textColor="@color/cc_attention_negative_text"
android:textAppearance="@style/TextAppearance.AppCompat.Medium"/>

<CheckBox
android:id="@+id/dialog_checkbox"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/emphasized_message"
android:focusable="true"
android:visibility="gone"
android:textSize="@dimen/font_size_dp_large"
android:layout_marginTop="@dimen/standard_spacer_double">
</CheckBox>

</RelativeLayout>

</ScrollView>
Expand Down
10 changes: 10 additions & 0 deletions app/src/org/commcare/CommCareApplication.java
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,8 @@ public class CommCareApplication extends MultiDexApplication {
private boolean invalidateCacheOnRestore;
private CommCareNoficationManager noficationManager;

private boolean backgroundSyncSafe;

@Override
public void onCreate() {
super.onCreate();
Expand Down Expand Up @@ -291,6 +293,14 @@ private void logFirstCommCareRun() {
}
}

public void setBackgroundSyncSafe(boolean backgroundSyncSafe){
this.backgroundSyncSafe = backgroundSyncSafe;
}

public boolean isBackgroundSyncSafe(){
return this.backgroundSyncSafe;
}

// Whether user is running CommCare for the first time after installation
public static boolean isFirstRunAfterInstall() {
SharedPreferences preferences =
Expand Down
28 changes: 26 additions & 2 deletions app/src/org/commcare/activities/CommCareActivity.java
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
package org.commcare.activities;


import static org.commcare.preferences.HiddenPreferences.isBackgroundSyncEnabled;
import static org.commcare.preferences.HiddenPreferences.isFlagSecureEnabled;
import static org.commcare.sync.ExternalDataUpdateHelper.COMMCARE_DATA_UPDATE_ACTION;

import android.annotation.TargetApi;
import android.content.Context;
import android.content.DialogInterface;
import android.content.IntentFilter;
import android.graphics.Rect;
import android.os.Build;
import android.os.Bundle;
import android.text.Spannable;
import android.util.DisplayMetrics;
Expand All @@ -32,6 +34,8 @@
import org.commcare.interfaces.WithUIController;
import org.commcare.logic.DetailCalloutListenerDefaultImpl;
import org.commcare.preferences.LocalePreferences;
import org.commcare.services.DataSyncCompleteBroadcastReceiver;
import org.commcare.services.FCMMessageData;
import org.commcare.session.CommCareSession;
import org.commcare.session.SessionFrame;
import org.commcare.session.SessionInstanceBuilder;
Expand Down Expand Up @@ -125,6 +129,7 @@ public abstract class CommCareActivity<R> extends AppCompatActivity
private ContainerFragment<Bundle> managedUiState;
private boolean isMainScreenBlocked;

private DataSyncCompleteBroadcastReceiver dataSyncCompleteBroadcastReceiver;

@Override
protected void onCreate(Bundle savedInstanceState) {
Expand Down Expand Up @@ -292,6 +297,18 @@ private void showPendingUserMessage() {
protected void onResume() {
super.onResume();
AudioController.INSTANCE.playPreviousAudio();

CommCareApplication.instance().setBackgroundSyncSafe(true);

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

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

@Override
Expand All @@ -315,6 +332,10 @@ protected void onPause() {

areFragmentsPaused = true;
AudioController.INSTANCE.systemInducedPause();

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

@Override
Expand Down Expand Up @@ -702,6 +723,9 @@ public boolean aTaskInProgress() {
return stateHolder != null && stateHolder.isCurrentTaskRunning();
}

// Dummy method, should be implemented by each Activity that is not safe for a background sync
public void alertPendingSync(FCMMessageData fcmMessageData) {}

/**
* Interface to perform additional setup code when adding an ActionBar
*/
Expand Down
8 changes: 8 additions & 0 deletions app/src/org/commcare/activities/CommcareListActivity.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import android.widget.ListAdapter;
import android.widget.ListView;

import org.commcare.CommCareApplication;
import org.commcare.dalvik.R;

/**
Expand Down Expand Up @@ -35,6 +36,13 @@ protected void onCreate(Bundle savedInstanceState) {
mListView.setOnItemClickListener(mOnClickListener);
}

@Override
protected void onResume() {
super.onResume();

CommCareApplication.instance().setBackgroundSyncSafe(true);
}

public int getLayoutResource() {
return R.layout.activity_commcare_list;
}
Expand Down
11 changes: 8 additions & 3 deletions app/src/org/commcare/activities/DispatchActivity.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,17 @@
import org.commcare.android.database.global.models.ApplicationRecord;
import org.commcare.android.database.user.models.SessionStateDescriptor;
import org.commcare.dalvik.R;
import org.commcare.models.AndroidSessionWrapper;
import org.commcare.preferences.DeveloperPreferences;
import org.commcare.recovery.measures.ExecuteRecoveryMeasuresActivity;
import org.commcare.recovery.measures.RecoveryMeasuresHelper;
import org.commcare.session.CommCareSession;
import org.commcare.suite.model.StackFrameStep;
import org.commcare.utils.AndroidShortcuts;
import org.commcare.utils.CommCareLifecycleUtils;
import org.commcare.utils.MultipleAppsUtil;
import org.commcare.utils.SessionUnavailableException;
import org.javarosa.core.model.condition.EvaluationContext;
import org.javarosa.core.services.locale.Localization;

import java.util.ArrayList;
Expand Down Expand Up @@ -47,7 +51,7 @@ public class DispatchActivity extends AppCompatActivity {
public static final String WAS_SHORTCUT_LAUNCH = "launch_from_shortcut";
public static final String START_FROM_LOGIN = "process_successful_login";
public static final String EXECUTE_RECOVERY_MEASURES = "execute_recovery_measures";

public static final String SESSION_REBUILD_REQUEST = "session_rebuild_request";
private static final int LOGIN_USER = 0;
private static final int HOME_SCREEN = 1;
public static final int INIT_APP = 2;
Expand Down Expand Up @@ -76,7 +80,7 @@ public class DispatchActivity extends AppCompatActivity {
private boolean waitingForActivityResultFromLogin;

boolean alreadyCheckedForAppFilesChange;

static final String REBUILD_SESSION = "rebuild_session";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Expand Down Expand Up @@ -193,7 +197,8 @@ private void dispatch() {
!shortcutExtraWasConsumed) {
// CommCare was launched from a shortcut
handleShortcutLaunch();
} else {
}
else {
launchHomeScreen();
}
} catch (SessionUnavailableException sue) {
Expand Down
5 changes: 5 additions & 0 deletions app/src/org/commcare/activities/EntityDetailActivity.java
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,11 @@ public void onCreateSessionSafe(Bundle savedInstanceState) {
isFinalSwipeActionEnabled = DeveloperPreferences.isDetailTabSwipeActionEnabled();
}

@Override
public boolean shouldListenToSyncComplete() {
return true;
}

@Override
public Pair<Detail, TreeReference> requestEntityContext() {
return mEntityContext;
Expand Down
11 changes: 11 additions & 0 deletions app/src/org/commcare/activities/EntitySelectActivity.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package org.commcare.activities;

import static org.commcare.activities.HomeScreenBaseActivity.RESULT_RESTART;

import androidx.appcompat.app.AppCompatActivity;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
Expand Down Expand Up @@ -302,6 +305,11 @@ private void setupLandscapeDualPaneView() {
message.setText(Localization.get("select.placeholder.message", new String[]{Localization.get("cchq.case")}));
}

@Override
public boolean shouldListenToSyncComplete() {
return true;
}

private void restoreExistingSelection(boolean isOrientationChange) {
// Restore detail screen for selection from landscape mode as we move into portrait mode.
if (isOrientationChange) {
Expand Down Expand Up @@ -569,6 +577,9 @@ public void onActivityResultSessionSafe(int requestCode, int resultCode, Intent
if (resultCode == RESULT_OK && !mViewMode) {
// create intent for return and store path
returnWithResult(intent);
} else if (resultCode == RESULT_RESTART) {
this.setResult(RESULT_RESTART, intent);
this.finish();
} else {
//Did we enter the detail from mapping mode? If so, go back to that
if (mResultIsMap) {
Expand Down
3 changes: 2 additions & 1 deletion app/src/org/commcare/activities/FormAndDataSyncer.java
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,8 @@ private <I extends CommCareActivity & PullTaskResultReceiver> void syncData(
DataPullRequester dataPullRequester, boolean blockRemoteKeyManagement) {

DataPullTask<PullTaskResultReceiver> dataPullTask = new DataPullTask<PullTaskResultReceiver>(
username, password, userId, server, activity, dataPullRequester, blockRemoteKeyManagement) {
username, password, userId, server, activity, dataPullRequester,
blockRemoteKeyManagement, false) {

@Override
protected void deliverResult(PullTaskResultReceiver receiver,
Expand Down
34 changes: 34 additions & 0 deletions app/src/org/commcare/activities/FormEntryActivity.java
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,9 @@
import org.commcare.models.AndroidSessionWrapper;
import org.commcare.models.FormRecordProcessor;
import org.commcare.models.database.SqlStorage;
import org.commcare.preferences.HiddenPreferences;
import org.commcare.services.FCMMessageData;
import org.commcare.services.PendingSyncAlertBroadcastReceiver;
import org.commcare.tasks.FormLoaderTask;
import org.commcare.tasks.SaveToDiskTask;
import org.commcare.tts.TextToSpeechCallback;
Expand Down Expand Up @@ -102,6 +105,7 @@
import androidx.core.app.ActivityCompat;

import static org.commcare.android.database.user.models.FormRecord.QuarantineReason_LOCAL_PROCESSING_ERROR;
import static org.commcare.sync.FirebaseMessagingDataSyncer.PENGING_SYNC_ALERT_ACTION;

/**
* Displays questions, animates transitions between
Expand Down Expand Up @@ -171,6 +175,9 @@ public class FormEntryActivity extends SaveSessionCommCareActivity<FormEntryActi

private boolean fullFormProfilingEnabled = false;
private EvaluationTraceReporter traceReporter;

private PendingSyncAlertBroadcastReceiver pendingSyncAlertBroadcastReceiver = new PendingSyncAlertBroadcastReceiver();

private TextToSpeechCallback mTTSCallback = new TextToSpeechCallback() {
@Override
public void initFailed() {
Expand Down Expand Up @@ -878,6 +885,8 @@ protected void onPause() {
PollSensorController.INSTANCE.stopLocationPolling();
}
TextToSpeechConverter.INSTANCE.stop();

unregisterReceiver(pendingSyncAlertBroadcastReceiver);
}

private void saveInlineVideoState() {
Expand Down Expand Up @@ -925,6 +934,12 @@ public void onResumeSessionSafe() {
restorePriorStates();

reportVideoUsageIfAny();

IntentFilter intentFilter = new IntentFilter(PENGING_SYNC_ALERT_ACTION);
registerReceiver(pendingSyncAlertBroadcastReceiver, intentFilter);

// Flag that a background sync shouldn't be triggered when this activity is in the foreground
CommCareApplication.instance().setBackgroundSyncSafe(false);
}

private void reportVideoUsageIfAny() {
Expand Down Expand Up @@ -1499,6 +1514,25 @@ public void onRequestPermissionsResult(int requestCode,
}
}

@Override
public void alertPendingSync(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.

missing @Override

if (!HiddenPreferences.isPendingSyncRequest(fcmMessageData.getUsername())) {
HiddenPreferences.setPendingSyncRequest(fcmMessageData);

if (!HiddenPreferences.isPendingSyncDialogDisabled()) {
StandardAlertDialog dialog = StandardAlertDialog.getBasicAlertDialogWithDisablingCheckbox(this,
Localization.get("background.sync.pending.form.entry.title"),
Localization.get("background.sync.pending.form.entry.detail"), (buttonView, isChecked) -> {
HiddenPreferences.setPendingSyncDialogDisabled(isChecked);
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think users are right decision makers on whether to show pending sync warnings or not. I think it should be an custom app setting instead or we should always show the warning.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The thinking here was to (1) let the user decide whether they would like to be disrupted every time a new sync request comes through (2) Make the users aware of sync being triggered after form submissions. I'm happy to go with your lead here but ultimately, it's the user who gets disrupted by this. What do you think?

Copy link
Contributor

Choose a reason for hiding this comment

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

Happy to let it be for now and we can come back to it later if needed.

});
dialog.setPositiveButton(Localization.get("dialog.ok"), (dialog1, which) -> {
dialog1.dismiss();
});
showAlertDialog(dialog);
}
}
}


public static class FormQueryException extends Exception {
public FormQueryException(String msg) {
Expand Down
6 changes: 6 additions & 0 deletions app/src/org/commcare/activities/HomeButtons.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@
import android.content.Intent;
import android.text.Spannable;
import android.view.View;
import android.widget.Toast;

import org.commcare.adapters.HomeCardDisplayData;
import org.commcare.adapters.SquareButtonViewHolder;
import org.commcare.dalvik.R;
import org.commcare.google.services.analytics.AnalyticsParamValue;
import org.commcare.google.services.analytics.FirebaseAnalyticsUtil;
import org.commcare.services.CommCareSessionService;
import org.commcare.utils.SessionUnavailableException;
import org.commcare.utils.StorageUtils;
import org.commcare.utils.SyncDetailCalculations;
Expand Down Expand Up @@ -110,6 +112,10 @@ private static View.OnClickListener getViewOldFormsListener(final StandardHomeAc

private static View.OnClickListener getSyncButtonListener(final StandardHomeActivity activity) {
return v -> {
if (CommCareSessionService.sessionAliveLock.isLocked()) {
Toast.makeText(activity, Localization.get("background.sync.user.sync.attempt.during.sync"), Toast.LENGTH_LONG).show();
return;
}
reportButtonClick(AnalyticsParamValue.SYNC_BUTTON);
activity.syncButtonPressed();
};
Expand Down
Loading