verificationLauncher = registerForActivityResult(
+ new ActivityResultContracts.StartActivityForResult(),
+ result -> {
+ if (result.getResultCode() == Activity.RESULT_OK) {
+ ConnectDownloadingFragment connectDownloadFragment = getConnectDownloadFragment();
+ if (connectDownloadFragment != null) {
+ connectDownloadFragment.onSuccessfulVerification();
+ }
+ }
+ });
+
+ @Override
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.screen_connect);
+ setTitle(getString(R.string.connect_title));
+ getIntentData();
+ updateBackButton();
+ Window window = getWindow();
+ window.setStatusBarColor(getResources().getColor(R.color.connect_status_bar_color));
+ ColorDrawable colorDrawable
+ = new ColorDrawable(getResources().getColor(R.color.connect_blue_color));
+ getSupportActionBar().setBackgroundDrawable(colorDrawable);
+ destinationListener = FirebaseAnalyticsUtil.getDestinationChangeListener();
+
+ NavHostFragment host = (NavHostFragment) getSupportFragmentManager().findFragmentById(R.id.nav_host_fragment_connect);
+ navController = host.getNavController();
+ navController.addOnDestinationChangedListener(destinationListener);
+
+ if (getIntent().getBooleanExtra("info", false)) {
+ ConnectJobRecord job = ConnectManager.getActiveJob();
+ int fragmentId = job.getStatus() == ConnectJobRecord.STATUS_DELIVERING ?
+ R.id.connect_job_delivery_progress_fragment :
+ R.id.connect_job_learning_progress_fragment;
+
+ boolean buttons = getIntent().getBooleanExtra("buttons", true);
+
+ Bundle bundle = new Bundle();
+ bundle.putBoolean("showLaunch", buttons);
+
+ NavOptions options = new NavOptions.Builder()
+ .setPopUpTo(navController.getGraph().getStartDestinationId(), true)
+ .build();
+ navController.navigate(fragmentId, bundle, options);
+ } else if (redirectionAction != null) {
+ ConnectManager.init(this);
+ ConnectManager.unlockConnect(this, success -> {
+ if (success) {
+ getJobDetails();
+ }
+ });
+ }
+ }
+
+ /**
+ * Returns the fragment ID based on the redirection action.
+ *
+ * This method determines which fragment should be displayed based on the value of the redirectionAction.
+ * It maps specific actions to their corresponding fragment IDs.
+ *
+ * @return The ID of the fragment to be displayed.
+ */
+ private int getFragmentId() {
+ int fragmentId;
+ if (redirectionAction.equals(CCC_OPPORTUNITY_SUMMARY_PAGE)) {
+ fragmentId = R.id.connect_job_intro_fragment;
+ } else if (redirectionAction.equals(CCC_LEARN_PROGRESS)) {
+ fragmentId = R.id.connect_job_learning_progress_fragment;
+ } else {
+ fragmentId = R.id.connect_job_delivery_progress_fragment;
+ }
+ return fragmentId;
+ }
+
+ private void getIntentData() {
+ redirectionAction = getIntent().getStringExtra("action");
+ opportunityId = getIntent().getStringExtra("opportunity_id");
+ }
+
+ /**
+ * Sets the fragment redirection based on the redirection action.
+ *
+ * This method determines the fragment to be displayed using the getFragmentId() method,
+ * prepares a bundle with additional data, and navigates to the appropriate fragment.
+ */
+ private void setFragmentRedirection(boolean ApiSuccess) {
+ if (ApiSuccess) {
+ int fragmentId = getFragmentId();
+
+ boolean buttons = getIntent().getBooleanExtra("buttons", true);
+ Bundle bundle = new Bundle();
+ bundle.putBoolean("showLaunch", buttons);
+
+ // Set the tab position in the bundle based on the redirection action
+ if (redirectionAction.equals(CCC_DELIVERY_PROGRESS)) {
+ bundle.putString("tabPosition", "0");
+ } else if (redirectionAction.equals(CCC_PAYMENTS)) {
+ bundle.putString("tabPosition", "1");
+ }
+
+ NavOptions options = new NavOptions.Builder()
+ .setPopUpTo(navController.getGraph().getStartDestinationId(), true)
+ .build();
+ navController.navigate(fragmentId, bundle, options);
+ }
+ }
+
+ @Override
+ public void onBackPressed() {
+ if (backButtonEnabled) {
+ super.onBackPressed();
+ }
+ }
+
+ @Override
+ protected void onDestroy() {
+ if (destinationListener != null) {
+ NavHostFragment navHostFragment = (NavHostFragment) getSupportFragmentManager()
+ .findFragmentById(R.id.nav_host_fragment_connect);
+ if (navHostFragment != null) {
+ NavController navController = navHostFragment.getNavController();
+ navController.removeOnDestinationChangedListener(destinationListener);
+ }
+ destinationListener = null;
+ }
+
+ super.onDestroy();
+ }
+
+ @Override
+ public CustomProgressDialog generateProgressDialog(int taskId) {
+ if (waitDialogEnabled) {
+ return CustomProgressDialog.newInstance(null, getString(R.string.please_wait), taskId);
+ }
+
+ return null;
+ }
+
+ public void setBackButtonEnabled(boolean enabled) {
+ backButtonEnabled = enabled;
+ }
+
+ public void setWaitDialogEnabled(boolean enabled) {
+ waitDialogEnabled = enabled;
+ }
+
+ private void updateBackButton() {
+ ActionBar actionBar = getSupportActionBar();
+ if (actionBar != null) {
+ actionBar.setDisplayShowHomeEnabled(isBackEnabled());
+ actionBar.setDisplayHomeAsUpEnabled(isBackEnabled());
+ }
+ }
+
+ @Override
+ protected boolean shouldShowBreadcrumbBar() {
+ return false;
+ }
+
+ @Override
+ public ResourceEngineListener getReceiver() {
+ return getConnectDownloadFragment();
+ }
+
+ @Nullable
+ private ConnectDownloadingFragment getConnectDownloadFragment() {
+ NavHostFragment navHostFragment =
+ (NavHostFragment) getSupportFragmentManager().findFragmentById(R.id.nav_host_fragment_connect);
+ Fragment currentFragment =
+ navHostFragment.getChildFragmentManager().getPrimaryNavigationFragment();
+ if (currentFragment instanceof ConnectDownloadingFragment) {
+ return (ConnectDownloadingFragment) currentFragment;
+ }
+ return null;
+ }
+
+ public void startAppValidation() {
+ Intent i = new Intent(this, CommCareVerificationActivity.class);
+ i.putExtra(CommCareVerificationActivity.KEY_LAUNCH_FROM_SETTINGS, true);
+ verificationLauncher.launch(i);
+ }
+
+ public void getJobDetails() {
+ ApiConnect.getConnectOpportunities(ConnectActivity.this, new IApiCallback() {
+ @Override
+ public void processSuccess(int responseCode, InputStream responseData) {
+ try {
+ String responseAsString = new String(StreamsUtil.inputStreamToByteArray(responseData));
+ if (responseAsString.length() > 0) {
+ //Parse the JSON
+ JSONArray json = new JSONArray(responseAsString);
+ List jobs = new ArrayList<>(json.length());
+ for (int i = 0; i < json.length(); i++) {
+ JSONObject obj = (JSONObject) json.get(i);
+ ConnectJobRecord job = ConnectJobRecord.fromJson(obj);
+ jobs.add(job);
+ if (job.getJobId() == Integer.parseInt(opportunityId)) {
+ ConnectManager.setActiveJob(job);
+ }
+ }
+ ConnectDatabaseHelper.storeJobs(ConnectActivity.this, jobs, true);
+ setFragmentRedirection(true);
+ }
+ } catch (IOException | JSONException | ParseException e) {
+ setFragmentRedirection(false);
+ Toast.makeText(ConnectActivity.this, R.string.connect_job_list_api_failure, Toast.LENGTH_SHORT).show();
+ Logger.exception("Parsing return from Opportunities request", e);
+ }
+ }
+
+ @Override
+ public void processFailure(int responseCode, IOException e) {
+ setFragmentRedirection(false);
+ Toast.makeText(ConnectActivity.this, R.string.connect_job_list_api_failure, Toast.LENGTH_SHORT).show();
+ Logger.log("ERROR", String.format(Locale.getDefault(), "Opportunities call failed: %d", responseCode));
+ }
+
+ @Override
+ public void processNetworkFailure() {
+ setFragmentRedirection(false);
+ Toast.makeText(ConnectActivity.this, R.string.recovery_network_unavailable, Toast.LENGTH_SHORT).show();
+ Logger.log("ERROR", "Failed (network)");
+ }
+
+ @Override
+ public void processOldApiError() {
+ setFragmentRedirection(false);
+ Toast.makeText(ConnectActivity.this, R.string.connect_job_list_api_failure, Toast.LENGTH_SHORT).show();
+ ConnectNetworkHelper.showOutdatedApiError(ConnectActivity.this);
+ }
+ });
+ }
+}
diff --git a/app/src/org/commcare/activities/connect/ConnectIdActivity.java b/app/src/org/commcare/activities/connect/ConnectIdActivity.java
new file mode 100644
index 0000000000..e883213afd
--- /dev/null
+++ b/app/src/org/commcare/activities/connect/ConnectIdActivity.java
@@ -0,0 +1,158 @@
+package org.commcare.activities.connect;
+
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.drawable.ColorDrawable;
+import android.os.Bundle;
+import android.view.Window;
+
+import org.commcare.activities.CommCareActivity;
+import org.commcare.android.database.connect.models.ConnectUserRecord;
+import org.commcare.connect.ConnectConstants;
+import org.commcare.connect.ConnectDatabaseHelper;
+import org.commcare.connect.ConnectManager;
+import org.commcare.dalvik.R;
+import org.commcare.fragments.connectId.ConnectIDSignupFragmentDirections;
+import org.commcare.fragments.connectId.ConnectIdBiometricConfigFragment;
+
+import androidx.fragment.app.Fragment;
+import androidx.navigation.NavController;
+import androidx.navigation.NavDirections;
+import androidx.navigation.fragment.NavHostFragment;
+
+import org.commcare.views.dialogs.CustomProgressDialog;
+
+public class ConnectIdActivity extends CommCareActivity {
+
+ public static boolean forgotPassword = false;
+ public static boolean forgotPin = false;
+ public static String recoverPhone;
+ public static String recoverSecret;
+ public static String recoveryAltPhone;
+ public static NavController controller;
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
+ if (requestCode == ConnectConstants.CONNECT_UNLOCK_PIN) {
+ getCurrentFragment().onActivityResult(requestCode, resultCode, data);
+ } else if (requestCode == ConnectConstants.CONNECTID_REQUEST_CODE) {
+ handleRedirection(data);
+ }
+ if (requestCode == RESULT_OK) {
+ finish();
+ }
+ }
+
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_connect_id);
+ NavHostFragment host2 = (NavHostFragment)getSupportFragmentManager().findFragmentById(R.id.nav_host_fragment_connectid);
+ controller = host2.getNavController();
+ handleRedirection(getIntent());
+ }
+
+ private void handleRedirection(Intent intent) {
+ String value = intent.getStringExtra("TASK");
+ if (value != null) {
+ switch (value) {
+ case ConnectConstants.BEGIN_REGISTRATION -> beginRegistration(this);
+ case ConnectConstants.VERIFY_PHONE -> beginSecondaryPhoneVerification(this);
+ }
+ }
+ }
+
+ @Override
+ public CustomProgressDialog generateProgressDialog(int taskId) {
+ return CustomProgressDialog.newInstance(null, getString(R.string.please_wait), taskId);
+ }
+
+ private ConnectIdBiometricConfigFragment getCurrentFragment() {
+ NavHostFragment navHostFragment =
+ (NavHostFragment)getSupportFragmentManager().findFragmentById(R.id.nav_host_fragment_connectid);
+ Fragment currentFragment =
+ navHostFragment.getChildFragmentManager().getPrimaryNavigationFragment();
+ if (currentFragment instanceof ConnectIdBiometricConfigFragment) {
+ return (ConnectIdBiometricConfigFragment)currentFragment;
+ }
+ return null;
+ }
+
+ public void beginRegistration(Context parent) {
+ forgotPassword = false;
+ forgotPin = false;
+ NavDirections navDirections = null;
+ switch (ConnectManager.getStatus()) {
+ case NotIntroduced :
+ navDirections = ConnectIDSignupFragmentDirections.actionConnectidSignupFragmentSelf()
+ .setCallingClass(ConnectConstants.CONNECT_REGISTRATION_PRIMARY_PHONE);
+ break;
+ case Registering :
+ ConnectUserRecord user = ConnectDatabaseHelper.getUser(parent);
+ int phase = user.getRegistrationPhase();
+ if (phase != ConnectConstants.CONNECT_NO_ACTIVITY) {
+ switch(phase) {
+ case ConnectConstants.CONNECT_REGISTRATION_PRIMARY_PHONE:
+ navDirections = ConnectIDSignupFragmentDirections.actionConnectidSignupFragmentSelf();
+ break;
+ case ConnectConstants.CONNECT_REGISTRATION_CONFIGURE_BIOMETRICS:
+ navDirections = ConnectIDSignupFragmentDirections.actionConnectidPhoneFragmentToConnectidBiometricConfig(phase);
+ break;
+// case ConnectConstants.CONNECT_REGISTRATION_VERIFY_PRIMARY_PHONE -> fragmentId = R.id.connectid_phone_verify;
+// case ConnectConstants.CONNECT_REGISTRATION_CHANGE_PRIMARY_PHONE -> fragmentId = R.id.connectid_phoneNo;
+// case ConnectConstants.CONNECT_REGISTRATION_ALTERNATE_PHONE -> fragmentId = R.id.connectid_secondary_phone_fragment;
+ case ConnectConstants.CONNECT_REGISTRATION_CONFIGURE_PIN:
+ case ConnectConstants.CONNECT_REGISTRATION_CONFIRM_PIN:
+ case ConnectConstants.CONNECT_REGISTRATION_CHANGE_PIN:
+ navDirections = ConnectIDSignupFragmentDirections.actionConnectidPhoneFragmentToConnectidPin(
+ phase, user.getPrimaryPhone(), user.getPassword());
+ break;
+ }
+ } else if (user.shouldForcePin()) {
+ navDirections = ConnectIDSignupFragmentDirections.
+ actionConnectidPhoneFragmentToConnectidPin(
+ ConnectConstants.CONNECT_UNLOCK_PIN,
+ user.getPrimaryPhone(),
+ user.getPassword());
+ } else if (user.shouldForcePassword()) {
+ navDirections = ConnectIDSignupFragmentDirections.
+ actionConnectidPhoneFragmentToConnectidPassword(
+ user.getPrimaryPhone(),
+ user.getPassword(), ConnectConstants.CONNECT_UNLOCK_PASSWORD);
+ } else {
+ navDirections = ConnectIDSignupFragmentDirections
+ .actionConnectidPhoneFragmentToConnectidBiometricConfig(
+ (ConnectConstants.CONNECT_UNLOCK_BIOMETRIC));
+ }
+ break;
+ }
+
+ if (navDirections != null) {
+ controller.navigate(navDirections);
+ }
+ }
+
+
+ public static void beginSecondaryPhoneVerification(Context parent) {
+ NavDirections navDirections = ConnectIDSignupFragmentDirections.actionConnectidPhoneFragmentToConnectidMessage
+ (parent.getString(R.string.connect_recovery_alt_title),
+ parent.getString(R.string.connect_recovery_alt_message),
+ ConnectConstants.CONNECT_VERIFY_ALT_PHONE_MESSAGE,
+ parent.getString(R.string.connect_password_fail_button),
+ parent.getString(R.string.connect_recovery_alt_change_button),null,null);
+ controller.navigate(navDirections);
+
+ }
+
+ public static void reset() {
+ recoverPhone = null;
+ recoveryAltPhone = null;
+ recoverSecret = null;
+ forgotPassword = false;
+ forgotPin = false;
+ }
+}
+
+
diff --git a/app/src/org/commcare/adapters/AppSelectAdapter.java b/app/src/org/commcare/adapters/AppSelectAdapter.java
new file mode 100644
index 0000000000..2e89f65e62
--- /dev/null
+++ b/app/src/org/commcare/adapters/AppSelectAdapter.java
@@ -0,0 +1,84 @@
+package org.commcare.adapters;
+
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.recyclerview.widget.RecyclerView;
+
+import org.commcare.android.database.global.models.ApplicationRecord;
+import org.commcare.dalvik.R;
+import org.commcare.utils.MultipleAppsUtil;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+/**
+ * Adapter for retrieving tiles representing installed apps for the user to select
+ *
+ * @author dviggiano
+ */
+public class AppSelectAdapter extends SquareButtonAdapter {
+
+ private final HomeCardDisplayData[] buttonData;
+
+ private final HashMap messagePayload = new HashMap<>();
+
+ public AppSelectAdapter(AppCompatActivity activity) {
+ super(activity);
+
+ List cards = new ArrayList<>();
+ for (ApplicationRecord record : MultipleAppsUtil.getUsableAppRecords()) {
+ HomeCardDisplayData card = HomeCardDisplayData.homeCardDataWithStaticText(record.getDisplayName(),
+ R.color.white,
+ R.drawable.ic_dimagi_logo,
+ R.color.cc_dark_cool_accent_color,
+ v -> {
+ //TODO: Handle button press
+ });
+
+ //TODO: Store app image in card and use it
+ //ImageView topBannerImageView = card.findViewById(R.id.main_top_banner);
+ //if (!CustomBanner.useCustomBannerFitToActivity(activity, topBannerImageView,
+ // CustomBanner.Banner.LOGIN)) {
+ // topBannerImageView.setImageResource(R.drawable.commcare_logo);
+ //}
+
+ cards.add(card);
+ }
+
+ buttonData = new HomeCardDisplayData[cards.size()];
+ cards.toArray(buttonData);
+ }
+
+ @Override
+ public void onBindViewHolder(RecyclerView.ViewHolder holder, int i, List