Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
dec323b
use SSD to restore incomplete form after form entry is interrupted by…
amstone326 Nov 10, 2017
9c6c538
be clearer about the ordering of login launch actions
amstone326 Nov 10, 2017
94bacd6
WIP: make sure app health actions get triggered later if we restore t…
amstone326 Nov 10, 2017
d25f865
Merge branch 'master' into return-to-form-after-session-expiration
amstone326 Nov 13, 2017
febf56f
checking if app health actions have occurred is workign
amstone326 Nov 13, 2017
4f44ff3
remove 2nd flag bc it doesn't add anything
amstone326 Nov 13, 2017
04d8dd7
remove unused return type and reorder methods
amstone326 Nov 13, 2017
447ae6c
remove noops and improve javadoc
amstone326 Nov 13, 2017
6139952
migration for SSD
amstone326 Nov 13, 2017
4db20cf
undo session length shortening
amstone326 Nov 13, 2017
0e3b8f1
remove unused import
amstone326 Nov 13, 2017
13e644b
protect against having no current SSD id
amstone326 Nov 13, 2017
4445d13
fix default boolean value
amstone326 Nov 13, 2017
d201034
cleanup; javadoc
amstone326 Nov 13, 2017
278fe35
remove unused method from V1 class
amstone326 Nov 13, 2017
4e9f806
remove 1 more unused field
amstone326 Nov 13, 2017
ef9c755
fix test failure
amstone326 Nov 13, 2017
587b757
make sure flags get cleared after login launch no matter what
amstone326 Nov 15, 2017
072fdfd
merge master
amstone326 Nov 15, 2017
6edc479
reorder method
amstone326 Nov 15, 2017
3872db0
merge master
amstone326 Dec 1, 2017
0f636c7
change earlier SSD references to use V1
amstone326 Dec 1, 2017
71b6b8c
store ID of interrupted SSD in prefs instead of storing in the SSD it…
amstone326 Dec 1, 2017
4008fad
undo noops
amstone326 Dec 1, 2017
5e4d7b1
Merge branch 'master' into return-to-form-after-session-expiration
amstone326 Dec 1, 2017
fc98e2b
fixes
amstone326 Dec 1, 2017
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
7 changes: 5 additions & 2 deletions app/src/org/commcare/activities/FormEntryActivity.java
Original file line number Diff line number Diff line change
Expand Up @@ -219,8 +219,11 @@ public boolean onMenuItemSelected(int featureId, MenuItem item) {
public void formSaveCallback() {
// note that we have started saving the form
savingFormOnKeySessionExpiration = true;
// start saving form, which will call the key session logout completion
// function when it finishes.

// Set flag that will allow us to restore this form when we log back in
CommCareApplication.instance().getCurrentSessionWrapper().setCurrentStateAsInterrupted();

// Start saving form; will trigger expireUserSession() on completion
saveIncompleteFormToDisk();
}

Expand Down
168 changes: 122 additions & 46 deletions app/src/org/commcare/activities/HomeScreenBaseActivity.java
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,10 @@ public abstract class HomeScreenBaseActivity<T> extends SyncCapableCommCareActiv
private boolean wasExternal = false;
private static final String WAS_EXTERNAL_KEY = "was_external";

// Indicates if 1 of the checks we performed in onCreate resulted in redirecting to a
// different activity or starting a UI-blocking task
private boolean redirectedInOnCreate = false;

@Override
public void onCreateSessionSafe(Bundle savedInstanceState) {
super.onCreateSessionSafe(savedInstanceState);
Expand Down Expand Up @@ -171,59 +175,113 @@ private void processFromShortcutLaunch() {
private void processFromLoginLaunch() {
if (getIntent().getBooleanExtra(DispatchActivity.START_FROM_LOGIN, false) &&
!loginExtraWasConsumed) {

getIntent().removeExtra(DispatchActivity.START_FROM_LOGIN);
loginExtraWasConsumed = true;

CommCareSession session = CommCareApplication.instance().getCurrentSession();
if (session.getCommand() != null) {
// restore the session state if there is a command.
// For debugging and occurs when a serialized
// session is stored upon login
isRestoringSession = true;
sessionNavigator.startNextSessionStep();
return;
try {
redirectedInOnCreate = doLoginLaunchChecksInOrder();
} finally {
// make sure this happens no matter what
clearOneTimeLoginActionFlags();
}
}
}

/**
* The order of operations in this method is very deliberate, and the logic for it is as
* follows:
* - If we're in demo mode, then we don't want to do any of the other checks because they're
* not relevant
* - Form and session restorations need to happen before we try to sync, because once we sync
* it could invalidate those states
* - Restoring a form that was interrupted by session expiration comes before restoring a saved
* session because it is of higher importance
* - Check for a post-update sync before doing a standard background form-send, since a sync
* action will include a form-send action
* - Once we're past that point, starting a background form-send process is safe, and we can
* safely do checkForPinLaunchConditions() at the same time
*/
private boolean doLoginLaunchChecksInOrder() {
if (isDemoUser()) {
showDemoModeWarning();
return false;
}

if (tryRestoringFormFromSessionExpiration()) {
return true;
}

if (tryRestoringSession()) {
return true;
}

if (CommCareApplication.instance().isPostUpdateSyncNeeded()) {
HiddenPreferences.setPostUpdateSyncNeeded(false);
triggerSync(false);
return true;
}

if (!CommCareApplication.instance().isSyncPending(false)) {
// Trigger off a regular unsent task processor, unless we're about to sync (which will
// then handle this in a blocking fashion)
if (!CommCareApplication.instance().isSyncPending(false)) {
checkAndStartUnsentFormsTask(false, false);
}
checkAndStartUnsentFormsTask(false, false);
}

if (isDemoUser()) {
showDemoModeWarning();
return;
}
if (checkForPinLaunchConditions()) {
return;
}
checkForPinLaunchConditions();

return false;
}

/**
* Regardless of what action(s) we ended up executing in doLoginLaunchChecksInOrder(), we
* don't want to end up trying the actions associated with these flags again at a later point.
* They either need to happen the first time on login, or not at all.
*/
private void clearOneTimeLoginActionFlags() {
HiddenPreferences.setPostUpdateSyncNeeded(false);
HiddenPreferences.clearInterruptedSSD();
}

private boolean tryRestoringFormFromSessionExpiration() {
SessionStateDescriptor existing = AndroidSessionWrapper.getFormStateForInterruptedUserSession();
if (existing != null) {
AndroidSessionWrapper state = CommCareApplication.instance().getCurrentSessionWrapper();
state.loadFromStateDescription(existing);
formEntry(CommCareApplication.instance().getCommCarePlatform()
.getFormContentUri(state.getSession().getForm()), state.getFormRecord());
return true;
}
return false;
}

private boolean tryRestoringSession() {
CommCareSession session = CommCareApplication.instance().getCurrentSession();
if (session.getCommand() != null) {
// Restore the session state if there is a command. This is for debugging and
// occurs when a serialized session was stored by a previous user session
isRestoringSession = true;
sessionNavigator.startNextSessionStep();
return true;
}
return false;
}

/**
* See if we should launch either the pin choice dialog, or the create pin activity directly
*
* @return true if we launched a dialog
*/
private boolean checkForPinLaunchConditions() {
private void checkForPinLaunchConditions() {
LoginMode loginMode = (LoginMode)getIntent().getSerializableExtra(LoginActivity.LOGIN_MODE);
if (loginMode == LoginMode.PRIMED) {
launchPinCreateScreen(loginMode);
return true;
}
if (loginMode == LoginMode.PASSWORD && DeveloperPreferences.shouldOfferPinForLogin()) {
} else if (loginMode == LoginMode.PASSWORD && DeveloperPreferences.shouldOfferPinForLogin()) {
boolean userManuallyEnteredPasswordMode = getIntent()
.getBooleanExtra(LoginActivity.MANUAL_SWITCH_TO_PW_MODE, false);
boolean alreadyDismissedPinCreation =
CommCareApplication.instance().getCurrentApp().getAppPreferences()
.getBoolean(HiddenPreferences.HAS_DISMISSED_PIN_CREATION, false);
if (!alreadyDismissedPinCreation || userManuallyEnteredPasswordMode) {
showPinChoiceDialog(loginMode);
return true;
}
}
return false;
}

private void showPinChoiceDialog(final LoginMode loginMode) {
Expand Down Expand Up @@ -403,22 +461,31 @@ int record = intent.getIntExtra("FORMRECORDS", -1);
}
break;
case GET_COMMAND:
boolean fetchNext = processReturnFromGetCommand(resultCode, intent);
if (!fetchNext) {
boolean continueWithSessionNav =
processReturnFromGetCommand(resultCode, intent);
if (!continueWithSessionNav) {
return;
}
break;
case GET_CASE:
fetchNext = processReturnFromGetCase(resultCode, intent);
if (!fetchNext) {
continueWithSessionNav = processReturnFromGetCase(resultCode, intent);
if (!continueWithSessionNav) {
return;
}
break;
case MODEL_RESULT:
fetchNext = processReturnFromFormEntry(resultCode, intent);
if (!fetchNext) {
continueWithSessionNav = processReturnFromFormEntry(resultCode, intent);
if (!continueWithSessionNav) {
return;
}
if (!CommCareApplication.instance().getSession().appHealthChecksCompleted()) {
// If we haven't done these checks yet in this user session, try to
if (checkForPendingAppHealthActions()) {
// If we kick one off, abandon the session navigation that we were
// going to proceed with, because it may be invalid now
return;
}
}
break;
case AUTHENTICATION_FOR_PIN:
if (resultCode == RESULT_OK) {
Expand Down Expand Up @@ -1024,19 +1091,18 @@ private void triggerSync(boolean triggeredByAutoSyncPending) {

@Override
public void onResumeSessionSafe() {
if (!sessionNavigationProceedingAfterOnResume) {
if (!redirectedInOnCreate && !sessionNavigationProceedingAfterOnResume) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
refreshActionBar();
}
attemptDispatchHomeScreen();
}

// reset these
redirectedInOnCreate = false;
sessionNavigationProceedingAfterOnResume = false;
}

/**
* Decides if we should actually be on the home screen, or else should redirect elsewhere
*/
private void attemptDispatchHomeScreen() {
try {
CommCareApplication.instance().getSession();
Expand All @@ -1047,19 +1113,29 @@ private void attemptDispatchHomeScreen() {
return;
}

if (CommCareApplication.instance().isPostUpdateSyncNeeded() && !isDemoUser()) {
HiddenPreferences.setPostUpdateSyncNeeded(false);
triggerSync(false);
} else if (CommCareApplication.instance().isSyncPending(false)) {
triggerSync(true);
} else if (UpdatePromptHelper.promptForUpdateIfNeeded(this)) {
return;
} else {
if (!checkForPendingAppHealthActions()) {
// Display the home screen!
refreshUI();
}
}

/**
*
* @return true if we kicked off any processes
*/
private boolean checkForPendingAppHealthActions() {
boolean result = false;
if (CommCareApplication.instance().isSyncPending(false)) {
triggerSync(true);
result = true;
} else if (UpdatePromptHelper.promptForUpdateIfNeeded(this)) {
result = true;
}

CommCareApplication.instance().getSession().setAppHealthChecksCompleted();
return result;
}

private void createAskUseOldDialog(final AndroidSessionWrapper state, final SessionStateDescriptor existing) {
final AndroidCommCarePlatform platform = CommCareApplication.instance().getCommCarePlatform();
String title = Localization.get("app.workflow.incomplete.continue.title");
Expand Down
58 changes: 44 additions & 14 deletions app/src/org/commcare/models/AndroidSessionWrapper.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import org.commcare.android.database.user.models.FormRecord;
import org.commcare.android.database.user.models.SessionStateDescriptor;
import org.commcare.modern.session.SessionWrapperInterface;
import org.commcare.preferences.HiddenPreferences;
import org.commcare.session.CommCareSession;
import org.commcare.session.SessionDescriptorUtil;
import org.commcare.session.SessionFrame;
Expand Down Expand Up @@ -121,28 +122,16 @@ public SessionStateDescriptor getExistingIncompleteCaseDescriptor() {
return null;
}

SqlStorage<FormRecord> storage =
CommCareApplication.instance().getUserStorage(FormRecord.class);

SqlStorage<SessionStateDescriptor> sessionStorage =
CommCareApplication.instance().getUserStorage(SessionStateDescriptor.class);

// TODO: This is really a join situation. Need a way to outline
// connections between tables to enable joining
// TODO: This is really a join situation. Need a way to outline connections between tables to enable joining

// See if this session's unique hash corresponds to any pending forms.
Vector<Integer> ids = sessionStorage.getIDsForValue(SessionStateDescriptor.META_DESCRIPTOR_HASH, ssd.getHash());

// Filter for forms which have actually been started.
for (int id : ids) {
try {
int recordId = Integer.valueOf(sessionStorage.getMetaDataFieldForRecord(id, SessionStateDescriptor.META_FORM_RECORD_ID));
if (!storage.exists(recordId)) {
sessionStorage.remove(id);
Log.d(TAG, "Removing stale ssd record: " + id);
continue;
}
if (FormRecord.STATUS_INCOMPLETE.equals(storage.getMetaDataFieldForRecord(recordId, FormRecord.META_STATUS))) {
if (ssdHasValidFormRecordId(id, sessionStorage)) {
return sessionStorage.read(id);
}
} catch (NumberFormatException nfe) {
Expand All @@ -152,6 +141,47 @@ public SessionStateDescriptor getExistingIncompleteCaseDescriptor() {
return null;
}

/**
* @return
*/
public static SessionStateDescriptor getFormStateForInterruptedUserSession() {
int idOfInterrupted = HiddenPreferences.getIdOfInterruptedSSD();
if (idOfInterrupted == -1) {
return null;
}
SqlStorage<SessionStateDescriptor> sessionStorage =
CommCareApplication.instance().getUserStorage(SessionStateDescriptor.class);
SessionStateDescriptor interrupted = sessionStorage.read(idOfInterrupted);
Copy link
Member

Choose a reason for hiding this comment

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

Two quick things:

  1. I don't know precisely why this could fail after fixing Added support to media files validation which are not url encoded. #2 below (no record exists), but I think that if this record isn't in session storage we shouldn't fail.
  2. Shared preferences are per-app, not per-user, so right now this can bleed across userdbs (which could cause Automatic GPS capture #1, but could also just cause bizarre behavior). Easiest fix is to append the user ID to the shared preferences key in the HiddenPreferences changes

return interrupted;
}

private static boolean ssdHasValidFormRecordId(int ssdId,
SqlStorage<SessionStateDescriptor> sessionStorage) {
SqlStorage<FormRecord> formRecordStorage =
CommCareApplication.instance().getUserStorage(FormRecord.class);

int correspondingFormRecordId = Integer.valueOf(
sessionStorage.getMetaDataFieldForRecord(ssdId, SessionStateDescriptor.META_FORM_RECORD_ID));

if (!formRecordStorage.exists(correspondingFormRecordId)) {
sessionStorage.remove(ssdId);
Log.d(TAG, "Removing stale ssd record: " + ssdId);
return false;
}

return FormRecord.STATUS_INCOMPLETE.equals(
formRecordStorage.getMetaDataFieldForRecord(correspondingFormRecordId, FormRecord.META_STATUS));
}

public void setCurrentStateAsInterrupted() {
if (sessionStateRecordId != -1) {
SqlStorage<SessionStateDescriptor> sessionStorage =
CommCareApplication.instance().getUserStorage(SessionStateDescriptor.class);
SessionStateDescriptor current = sessionStorage.read(sessionStateRecordId);
HiddenPreferences.setInterruptedSSD(current.getID());
}
}

public void commitStub() {
//TODO: This should now be locked somehow
SqlStorage<FormRecord> storage = CommCareApplication.instance().getUserStorage(FormRecord.class);
Expand Down
19 changes: 19 additions & 0 deletions app/src/org/commcare/preferences/HiddenPreferences.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ public class HiddenPreferences {
public final static String LAST_UPDATE_ATTEMPT = "cc-last_up";
public final static String LAST_SYNC_ATTEMPT = "last-ota-restore";
public final static String LOG_LAST_DAILY_SUBMIT = "log_prop_last_daily";
public final static String ID_OF_INTERRUPTED_SSD = "interrupted-ssd-id";

// Preferences that are only ever set by being sent down from HQ via the profile file
public final static String AUTO_SYNC_FREQUENCY = "cc-autosync-freq";
Expand Down Expand Up @@ -194,4 +195,22 @@ public static void setPostUpdateSyncNeeded(boolean b) {
CommCareApplication.instance().getCurrentApp().getAppPreferences().edit()
.putBoolean(POST_UPDATE_SYNC_NEEDED, b).apply();
}

public static void setInterruptedSSD(int ssdId) {
String currentUserId = CommCareApplication.instance().getCurrentUserId();
CommCareApplication.instance().getCurrentApp().getAppPreferences().edit()
.putInt(ID_OF_INTERRUPTED_SSD + currentUserId, ssdId).apply();
}

public static int getIdOfInterruptedSSD() {
String currentUserId = CommCareApplication.instance().getCurrentUserId();
return CommCareApplication.instance().getCurrentApp().getAppPreferences()
.getInt(ID_OF_INTERRUPTED_SSD + currentUserId, -1);
}

public static void clearInterruptedSSD() {
String currentUserId = CommCareApplication.instance().getCurrentUserId();
CommCareApplication.instance().getCurrentApp().getAppPreferences().edit()
.putInt(ID_OF_INTERRUPTED_SSD + currentUserId, -1).apply();
}
}
Loading