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

ConnectID Foundation #2847

Open
wants to merge 21 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
1e72821
Created branch with foundational code for ConnectID and later Connect.
OrangeAndGreen Sep 12, 2024
56bb1fb
Added wrapper class for ConnectID API calls
OrangeAndGreen Sep 12, 2024
1351a0a
Added unit tests for encryption, and mock encryption provider to supp…
OrangeAndGreen Sep 12, 2024
fcfbb71
Added externalizables to test.
OrangeAndGreen Sep 16, 2024
b58c6eb
Merge branch 'master' of https://github.com/dimagi/commcare-android i…
OrangeAndGreen Dec 4, 2024
93314ed
Addressing PR feedback.
OrangeAndGreen Dec 4, 2024
906137d
Simplified linkHqWorker to take ConnectLinkedAppRecord from caller in…
OrangeAndGreen Dec 4, 2024
a8bcc43
Better error handling when linkHqWorker fails
OrangeAndGreen Dec 4, 2024
445ec10
Added ServerUrls.buildEndpoint helper method to build new endpoints u…
OrangeAndGreen Dec 4, 2024
b4dad76
Added SsoToken class with common code for retrieving token info from …
OrangeAndGreen Dec 4, 2024
9312dfd
Removed date-related functions from network helper class, using exist…
OrangeAndGreen Dec 4, 2024
3d9b7ea
Extracted common code for building POST data from parameters, to be u…
OrangeAndGreen Dec 4, 2024
b2aa121
Moved Connect-related classes to v2.55 section
OrangeAndGreen Dec 4, 2024
ea2db93
Added static helper class to lazy load KeyStore singleton.
OrangeAndGreen Dec 4, 2024
d412539
Lint
OrangeAndGreen Dec 5, 2024
b842ebd
Merge branch 'master' into dv/connectid_foundation
OrangeAndGreen Dec 5, 2024
fecd2bc
Merge branch 'master' of https://github.com/dimagi/commcare-android i…
OrangeAndGreen Dec 17, 2024
aa616b4
-pr review bug fixes
pm-dimagi Dec 19, 2024
918d1af
Merge branch 'master' into dv/connectid_foundation
OrangeAndGreen Dec 31, 2024
66cf541
- retrofit changes for api call
pm-dimagi Jan 7, 2025
4f42de2
-bug fix for language change
pm-dimagi Jan 7, 2025
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
3 changes: 3 additions & 0 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,9 @@ dependencies {
implementation 'joda-time:joda-time:2.9.4'
implementation 'net.zetetic:android-database-sqlcipher:4.5.3@aar'
implementation 'androidx.sqlite:sqlite:2.2.0'
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.okhttp3:logging-interceptor:4.11.0'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
implementation('org.apache.james:apache-mime4j:0.7.2') {
exclude module: 'commons-io'
}
Expand Down
7 changes: 6 additions & 1 deletion app/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@
<string name="key_server">https://pact.dimagi.com/keys/getkey</string>
<string name="your_comment">Your comment</string>
<string name="support_email_address_default">[email protected]</string>

<string name="ConnectFetchDbKeyURL">https://connectid.dimagi.com/users/fetch_db_key</string>
<string name="ConnectTokenURL">https://connectid.dimagi.com/o/token/</string>
<string name="ConnectHeartbeatURL">https://connectid.dimagi.com/users/heartbeat</string>
<!-- region: All strings for multiple apps and app-agnostic properties -->

<string name="manager_activity_name">App Manager</string>
Expand Down Expand Up @@ -384,6 +386,7 @@
<string name="recovery_forms_send_error" cc:translatable="true">Error while sending forms</string>
<string name="recovery_forms_state_unavailable" cc:translatable="true">Forms are not available. Make sure your phone storage is available</string>
<string name="recovery_network_unavailable" cc:translatable="true">No network connection. Please check your internet and try again.</string>
<string name="recovery_network_outdated" cc:translatable="true">The app is outdated and can no longer communicate with the server. Please update the app on the Google Play Store.</string>
<string name="recovery_app_manager" cc:translatable="true">Go to App Manager</string>
<string name="recovery_retry" cc:translatable="true">Retry Recovery</string>

Expand Down Expand Up @@ -456,4 +459,6 @@
<string name="fcm_default_notification_channel">notification-channel-push-notifications</string>
<string name="app_with_id_not_found">Required CommCare App is not installed on device</string>
<string name="audio_recording_notification">Audio Recording Notification</string>

<string name="connect_db_corrupt">A problem occurred with the database, please recover your account.</string>
</resources>
37 changes: 24 additions & 13 deletions app/src/org/commcare/CommCareApplication.java
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@
import org.commcare.utils.CommCareUtil;
import org.commcare.utils.CrashUtil;
import org.commcare.utils.DeviceIdentifier;
import org.commcare.utils.EncryptionKeyProvider;
import org.commcare.utils.FileUtil;
import org.commcare.utils.FirebaseMessagingUtil;
import org.commcare.utils.GlobalConstants;
Expand Down Expand Up @@ -202,6 +203,7 @@ public class CommCareApplication extends Application implements LifecycleEventOb

private boolean invalidateCacheOnRestore;
private CommCareNoficationManager noficationManager;
private EncryptionKeyProvider encryptionKeyProvider;

private boolean backgroundSyncSafe;

Expand Down Expand Up @@ -257,6 +259,9 @@ public void onCreate() {

FirebaseMessagingUtil.verifyToken();

//Create standard provider
setEncryptionKeyProvider(new EncryptionKeyProvider());

customiseOkHttp();

setRxJavaGlobalHandler();
Expand All @@ -279,7 +284,7 @@ protected void turnOnStrictMode() {
}

@Override
public void onConfigurationChanged(Configuration newConfig) {
public void onConfigurationChanged(@NonNull Configuration newConfig) {
super.onConfigurationChanged(newConfig);
LocalePreferences.saveDeviceLocale(newConfig.locale);
shubham1g5 marked this conversation as resolved.
Show resolved Hide resolved
}
Expand All @@ -300,11 +305,11 @@ private void logFirstCommCareRun() {
}
}

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

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

Expand Down Expand Up @@ -345,11 +350,11 @@ private void configureCommCareEngineConstantsAndStaticRegistrations() {
// md5 hasher. Major speed improvements.
AndroidClassHasher.registerAndroidClassHashStrategy();

ActivityManager am = (ActivityManager)getSystemService(ACTIVITY_SERVICE);
ActivityManager am = (ActivityManager) getSystemService(ACTIVITY_SERVICE);
int memoryClass = am.getMemoryClass();

PerformanceTuningUtil.updateMaxPrefetchCaseBlock(
PerformanceTuningUtil.guessLargestSupportedBulkCaseFetchSizeFromHeap(memoryClass * 1024 * 1024));
PerformanceTuningUtil.guessLargestSupportedBulkCaseFetchSizeFromHeap((long) memoryClass * 1024 * 1024));
}

public void startUserSession(byte[] symmetricKey, UserKeyRecord record, boolean restoreSession) {
Expand Down Expand Up @@ -425,12 +430,13 @@ synchronized public FirebaseAnalytics getAnalyticsInstance() {
analyticsInstance = FirebaseAnalytics.getInstance(this);
}
analyticsInstance.setUserId(getUserIdOrNull());

return analyticsInstance;
}

public int[] getCommCareVersion() {
String[] components = BuildConfig.VERSION_NAME.split("\\.");
int[] versions = new int[] {0, 0, 0};
int[] versions = new int[]{0, 0, 0};
for (int i = 0; i < components.length; i++) {
versions[i] = Integer.parseInt(components[i]);
}
Expand Down Expand Up @@ -475,7 +481,7 @@ public void initializeGlobalResources(CommCareApp app) {

@NonNull
public String getPhoneId() {
/**
/*
* https://source.android.com/devices/tech/config/device-identifiers
* https://issuetracker.google.com/issues/129583175#comment10
* Starting from Android 10, apps cannot access non-resettable device ids unless they have special career permission.
Expand Down Expand Up @@ -519,7 +525,7 @@ private void setRoots() {
private void initializeAnAppOnStartup() {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
String lastAppId = prefs.getString(LoginActivity.KEY_LAST_APP, "");
if (!"".equals(lastAppId)) {
if (!lastAppId.isEmpty()) {
ApplicationRecord lastApp = MultipleAppsUtil.getAppById(lastAppId);
if (lastApp == null || !lastApp.isUsable()) {
AppUtils.initFirstUsableAppRecord();
Expand Down Expand Up @@ -550,7 +556,6 @@ public void initializeAppResources(CommCareApp app) {
} catch (Exception e) {
Log.i("FAILURE", "Problem with loading");
Log.i("FAILURE", "E: " + e.getMessage());
e.printStackTrace();
ForceCloseLogger.reportExceptionInBg(e);
CrashUtil.reportException(e);
resourceState = STATE_CORRUPTED;
Expand Down Expand Up @@ -736,7 +741,7 @@ public void onServiceConnected(ComponentName className, IBinder service) {
synchronized (serviceLock) {
mCurrentServiceBindTimeout = MAX_BIND_TIMEOUT;

mBoundService = ((CommCareSessionService.LocalBinder)service).getService();
mBoundService = ((CommCareSessionService.LocalBinder) service).getService();
mBoundService.showLoggedInNotification(null);

// Don't let anyone touch this until it's logged in
Expand Down Expand Up @@ -922,7 +927,6 @@ public static boolean areAutomatedActionsInvalid() {

/**
* Whether the current login is a "demo" mode login.
*
* Returns a provided default value if there is no active user login
*/
public static boolean isInDemoMode(boolean defaultValue) {
Expand Down Expand Up @@ -977,8 +981,7 @@ public CommCareSessionService getSession() {
public static boolean isSessionActive() {
try {
return CommCareApplication.instance().getSession() != null;
}
catch (SessionUnavailableException e){
} catch (SessionUnavailableException e) {
return false;
}
}
Expand Down Expand Up @@ -1158,6 +1161,14 @@ public void setInvalidateCacheFlag(boolean b) {
invalidateCacheOnRestore = b;
}

public void setEncryptionKeyProvider(EncryptionKeyProvider provider) {
encryptionKeyProvider = provider;
}

public EncryptionKeyProvider getEncryptionKeyProvider() {
return encryptionKeyProvider;
}

public PrototypeFactory getPrototypeFactory(Context c) {
return AndroidPrototypeFactorySetup.getPrototypeFactory(c);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
package org.commcare.android.database.connect.models;

import org.commcare.android.storage.framework.Persisted;
import org.commcare.models.framework.Persisting;
import org.commcare.modern.database.Table;
import org.commcare.modern.models.MetaField;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

@Table(ConnectAppRecord.STORAGE_KEY)
public class ConnectAppRecord extends Persisted implements Serializable {
/**
* Name of table that stores app info for Connect jobs
*/
public static final String STORAGE_KEY = "connect_apps";

public static final String META_JOB_ID = "job_id";
public static final String META_DOMAIN = "cc_domain";
public static final String META_APP_ID = "cc_app_id";
public static final String META_NAME = "name";
public static final String META_DESCRIPTION = "description";
public static final String META_ORGANIZATION = "organization";
public static final String META_PASSING_SCORE = "passing_score";
public static final String META_INSTALL_URL = "install_url";
public static final String META_MODULES = "learn_modules";

@Persisting(1)
@MetaField(META_JOB_ID)
private int jobId;
@Persisting(2)
private boolean isLearning;
@Persisting(3)
@MetaField(META_DOMAIN)
private String domain;
@Persisting(4)
@MetaField(META_APP_ID)
private String appId;
@Persisting(5)
@MetaField(META_NAME)
private String name;
@Persisting(6)
@MetaField(META_DESCRIPTION)
private String description;
@Persisting(7)
@MetaField(META_ORGANIZATION)
private String organization;

@Persisting(8)
@MetaField(META_PASSING_SCORE)
private int passingScore;
@Persisting(9)
@MetaField(META_INSTALL_URL)
private String installUrl;
@Persisting(10)
private Date lastUpdate;

private List<ConnectLearnModuleSummaryRecord> learnModules;

public ConnectAppRecord() {

}

public static ConnectAppRecord fromJson(JSONObject json, int jobId, boolean isLearning) throws JSONException {
ConnectAppRecord app = new ConnectAppRecord();

app.jobId = jobId;
app.isLearning = isLearning;

app.domain = json.has(META_DOMAIN) ? json.getString(META_DOMAIN) : "";
app.appId = json.has(META_APP_ID) ? json.getString(META_APP_ID) : "";
app.name = json.has(META_NAME) ? json.getString(META_NAME) : "";
app.description = json.has(META_DESCRIPTION) ? json.getString(META_DESCRIPTION) : "";
app.organization = json.has(META_ORGANIZATION) ? json.getString(META_ORGANIZATION) : "";
app.passingScore = json.has(META_PASSING_SCORE) && !json.isNull(META_PASSING_SCORE) ? json.getInt(META_PASSING_SCORE) : -1;
app.installUrl = json.has(META_INSTALL_URL) ? json.getString(META_INSTALL_URL) : "";

JSONArray array = json.getJSONArray(META_MODULES);
app.learnModules = new ArrayList<>();
for(int i=0; i<array.length(); i++) {
JSONObject obj = (JSONObject)array.get(i);
app.learnModules.add(ConnectLearnModuleSummaryRecord.fromJson(obj, i));
}

return app;
}

public boolean getIsLearning() { return isLearning; }
public int getJobId() { return jobId; }
public void setJobId(int jobId) { this.jobId = jobId; }

public String getAppId() { return appId; }
public String getDomain() { return domain; }
public int getPassingScore() { return passingScore; }

public List<ConnectLearnModuleSummaryRecord> getLearnModules() { return learnModules; }
public String getInstallUrl() { return installUrl; }
public void setLearnModules(List<ConnectLearnModuleSummaryRecord> modules) { learnModules = modules; }
public void setLastUpdate(Date lastUpdate) { this.lastUpdate = lastUpdate; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package org.commcare.android.database.connect.models;

import org.commcare.android.storage.framework.Persisted;
import org.commcare.connect.network.ConnectNetworkHelper;
import org.commcare.models.framework.Persisting;
import org.commcare.modern.database.Table;
import org.commcare.modern.models.MetaField;
import org.javarosa.core.model.utils.DateUtils;
import org.json.JSONException;
import org.json.JSONObject;

import java.io.Serializable;
import java.text.ParseException;
import java.util.Date;

/**
* Data class for holding info related to a Connect job assessment
*
* @author dviggiano
*/
@Table(ConnectJobAssessmentRecord.STORAGE_KEY)
public class ConnectJobAssessmentRecord extends Persisted implements Serializable {
/**
* Name of database that stores Connect job assessments
*/
public static final String STORAGE_KEY = "connect_assessments";

public static final String META_JOB_ID = "id";
public static final String META_DATE = "date";
public static final String META_SCORE = "score";
public static final String META_PASSING_SCORE = "passing_score";
public static final String META_PASSED = "passed";

@Persisting(1)
@MetaField(META_JOB_ID)
private int jobId;
@Persisting(2)
@MetaField(META_DATE)
private Date date;
@Persisting(3)
@MetaField(META_SCORE)
private int score;
@Persisting(4)
@MetaField(META_PASSING_SCORE)
private int passingScore;
@Persisting(5)
@MetaField(META_PASSED)
private boolean passed;
@Persisting(6)
private Date lastUpdate;

public ConnectJobAssessmentRecord() {

}

public static ConnectJobAssessmentRecord fromJson(JSONObject json, int jobId) throws JSONException, ParseException {
ConnectJobAssessmentRecord record = new ConnectJobAssessmentRecord();

record.lastUpdate = new Date();

record.jobId = jobId;
record.date = json.has(META_DATE) ? DateUtils.parseDate(json.getString(META_DATE)) : new Date();
record.score = json.has(META_SCORE) ? json.getInt(META_SCORE) : -1;
record.passingScore = json.has(META_PASSING_SCORE) ? json.getInt(META_PASSING_SCORE) : -1;
record.passed = json.has(META_PASSED) && json.getBoolean(META_PASSED);

return record;
}

public Date getDate() { return date; }
public int getScore() { return score; }
public int getPassingScore() { return passingScore; }

public void setLastUpdate(Date date) { lastUpdate = date; }
}
Loading
Loading